﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Reflection;
using System.Collections;
using System.Security.Cryptography;
using System.Globalization;

using SysPath = System.IO.Path;
using SysEnv = System.Environment;

using King.Text;
using King.Extensions;
using King.Reflection;

namespace King.TeamFoundation {

    public class TfsLoader {

        private static TfsInfo s_info = new TfsInfo();

        public static IEnumerable<TfsInfo> Infos() {
            if (s_infos == null) {
                var infos =
                    from o in Assembly.GetExecutingAssembly().GetTypes()
                    where o.IsDefined<TfsObjectAttribute>()
                    select new TfsInfo(o);
                s_infos = infos.ToArray();
            }
            return s_infos;
        }
        public static TfsInfo GetInfo(Type type) {
            if (s_infosByType == null)
                s_infosByType = Infos().ToDictionary(o => o.Type);
            return s_infosByType.GetValueOrDefault(type) ?? s_info;
        }
        public static TfsInfo GetInfo(string extension) {
            if (s_infosByExtension == null)
                s_infosByExtension = Infos().Where(o => o.Extension != null).ToCaseInsensitiveDictionary(o => o.Extension);
            return s_infosByExtension.GetValueOrDefault(extension);
        }
        public static TfsInfo GetInfo(Guid guid) {
            if (s_infosByGuid == null)
                s_infosByGuid = Infos().Where(o => o.Guid != null).ToDictionary(o => (Guid)o.Guid);
            return s_infosByGuid.GetValueOrDefault(guid);
        }

        private static IEnumerable<TfsInfo> s_infos;
        private static Dictionary<Type, TfsInfo> s_infosByType;
        private static Dictionary<string, TfsInfo> s_infosByExtension;
        private static Dictionary<Guid, TfsInfo> s_infosByGuid;

        private TfsWorkspace m_workspace;
        private TfsCollection m_collection;
        private Dictionary<string, TfsUser> m_usersByName;
        private string m_currentDirectory;

        public TfsLoader()
            : this(Environment.CurrentDirectory) {
        }
        public TfsLoader(string currentDirectory) {
            m_currentDirectory = currentDirectory.NormalizeDirectory();
        }

        public TfsWorkspace Workspace {
            get {
                if (m_workspace == null) {
                    var workfold = new TfWorkfold() {
                        WorkingDirectory = m_currentDirectory,
                    }.ReadToEnd().StringSplit(Environment.NewLine).ToQueue();

                    workfold.Dequeue();
                    var match = Regex.Match(workfold.Dequeue(), "Workspace : (?<name>[^ ]*) [(](?<owner>[^)]*)[)]");
                    var name = match.Groups["name"].Value;
                    var owner = match.Groups["owner"].Value;

                    match = Regex.Match(workfold.Dequeue(), "Collection: (?<name>.*)");
                    var collectionUrl = new Uri(match.Groups["name"].Value).NormalizeUrlDirectory();
                    if (m_collection == null)
                        m_collection = new TfsCollection(this, collectionUrl);

                    if (!collectionUrl.Equals(m_collection.Url))
                        throw new ArgumentException("Collection url different than workspace collection url.");

                    m_workspace = m_collection.GetWorkspace(name, owner);
                    m_workspace.CacheMappings(workfold.Select(o => TfsMapping.Parse(m_workspace, o)));
                    m_workspace.Status();
                }

                return m_workspace;
            }
        }
        public TfsCollection Collection {
            get {
                if (m_collection == null)
                    m_collection = Workspace.Collection;
                return m_collection;
            }
        }
        public TfsUser GetUser(string userName) {
            if (m_usersByName == null)
                m_usersByName = new Dictionary<string, TfsUser>();
            var user = m_usersByName.GetValueOrDefault(userName);
            if (user == null)
                user = m_usersByName[userName] = new TfsUser(this, userName);
            return user;
        }

        public IEnumerable<TfsItem> LoadProjectGraph() {
            var collection = Collection;
            var labFolder = Collection.LabFolder;
            var projectExtensions =
                from o in TfsLoader.Infos()
                where o.IsProject
                select o.Extension;

            var extensions = projectExtensions.Concat(".sln");

            return labFolder.GetItems(extensions).ToArray();
        }
    }

    [AttributeUsage(AttributeTargets.All, Inherited = true)]
    public class TfsObjectAttribute : Attribute {
        public string Extension { get; set; }
        public string Guid { get; set; }
        public bool SolutionObject { get; set; }
        public bool IsManagedProject { get; set; }
        public bool IsUnmanagedProject { get; set; }
        public object DefaultPlatform { get; set; }
        public string DependentFiles { get; set; }
        public string PathIncludes { get; set; }
        public string PathAttributes { get; set; }
        public string PathElements { get; set; }
    }
    public class TfsInfo {

        private Type m_type;
        private string m_extension;
        private Guid? m_guid;
        private bool m_isSolutionObject;
        private bool m_isManagedProject;
        private bool m_isUnmanagedProject;
        private TfsPlatform? m_defaultPlatform;
        private MethodInfo m_toXml;
        private MethodInfo m_fromXml;
        private IEnumerable<string> m_dependentItems;
        private HashSet<string> m_pathAttributes;
        private HashSet<string> m_pathElements;
        private HashSet<string> m_pathIncludes;

        internal TfsInfo() {
            m_dependentItems = Enumerable.Empty<string>();
            m_pathAttributes =
            m_pathElements =
            m_pathIncludes =
                new HashSet<string>();
        }
        internal TfsInfo(Type type) {
            m_type = type;
            var attribute = type.GetAttribute<TfsObjectAttribute>();

            m_extension = attribute.Extension;
            if (attribute.Guid != null)
                m_guid = new Guid(attribute.Guid);
            m_isSolutionObject = attribute.SolutionObject;
            m_isManagedProject = attribute.IsManagedProject;
            m_isUnmanagedProject = attribute.IsUnmanagedProject;
            m_defaultPlatform = (TfsPlatform?)attribute.DefaultPlatform;
            if (m_defaultPlatform == null && m_isUnmanagedProject)
                m_defaultPlatform = TfsConfiguration.GetDefaultUnmanagedPlatform();

            m_toXml = m_type.Get<MethodInfo>("ToXml", typeof(string)).Single();
            m_fromXml = m_type.Get<MethodInfo>("FromXml", typeof(XElement)).Single();

            m_dependentItems = attribute.DependentFiles.StringSplit(" ");

            m_pathAttributes = attribute.PathAttributes.StringSplit(" ").ToHashSet();
            m_pathElements = attribute.PathElements.StringSplit(" ").ToHashSet();
            m_pathIncludes = attribute.PathIncludes.StringSplit(" ").ToHashSet();
        }

        public Type Type {
            get { return m_type; }
        }
        public string Extension {
            get { return m_extension; }
        }
        public bool IsProject {
            get { return m_type.IsSubclassOf<TfsProject>(); }
        }
        public bool IsSolutionObject {
            get { return m_isSolutionObject; }
        }
        public bool IsSolutionFolder {
            get { return m_type == typeof(TfsSolutionFolder); }
        }
        public Guid? Guid {
            get { return m_guid; }
        }
        public bool IsManagedProject {
            get { return m_isManagedProject; }
        }
        public bool IsUnmanagedProject {
            get { return m_isUnmanagedProject; }
        }
        public TfsPlatform? DefaultPlatform {
            get { return m_defaultPlatform; }
        }
        public string XName {
            get { return m_type.Name.Substring(3).ToLowerFirst(); }
        }
        public XElement ToXml(string source) {
            if (m_toXml == null)
                return XElement.Load(source);

            return (XElement)m_toXml.InvokeStatic(source);
        }
        public string FromXml(XElement source) {
            if (m_fromXml == null)
                return source.ToString();

            return (string)m_fromXml.InvokeStatic(source);
        }
        public IEnumerable<string> DependentItems() {
            return m_dependentItems;
        }

        public override string ToString() {
            if (Extension != null)
                return "*" + m_extension;

            if (IsSolutionFolder)
                return "Solution Folder";

            return m_type.Name;
        }

        internal HashSet<string> PathAttributes() {
            return m_pathAttributes;
        }
        internal HashSet<string> PathElements() {
            return m_pathElements;
        }
        internal HashSet<string> PathIncludes() {
            return m_pathIncludes;
        }
    }

    [Flags]
    public enum TfsChange {
        None = 0,
        Edit = 1 << 0,
        Merge = 1 << 1,
        Branch = 1 << 2,
        Rename = 1 << 3,
        Add = 1 << 4,
        Delete = 1 << 5,

        // Not under source control
        New = 1 << 10,
        Missing = 1 << 11,
    }
    public enum TfsLock {
        None,
        Checkout,
        Checkin,
    }
    public class TfsStatus {

        private class Context {

            private static void ProcessItems(IEnumerable<TfsPath> items, Action<IEnumerable<TfsPath>, Uri> action) {
                var groupByDir = items.GroupBy(o => o.Folder.PathUrl);
                foreach (var group in groupByDir.Where(o => o.MoreThanOne()))
                    action(group, group.Key);

                var singletonGroups = groupByDir.Where(o => o.OnlyOne()).ToArray();
                var commonParentDir = singletonGroups.Aggregate(default(Uri), (a, o) => a.CommonParent(o.Key));
                if (commonParentDir != null)
                    action(singletonGroups.SelectMany(o => o), commonParentDir);
            }
            private static void Add(IEnumerable<TfsPath> items, Uri path) {
                new TfAdd() {
                    Itemspec = new TextJoin(" ") { 
                    items.Select(o => new CmdLineArg(path.MakeRelativePath(o.Path)))
                },
                    WorkingDirectory = path.LocalPath,
                }.ReadToEnd();
            }
            private static void Checkout(IEnumerable<TfsPath> items, Uri path) {
                new TfCheckout() {
                    Itemspec = new TextJoin(" ") { 
                        items.Select(o => new CmdLineArg(path.MakeRelativePath(o.Path)))
                    },
                    WorkingDirectory = path.LocalPath,
                }.ReadToEnd();
            }

            private List<TfsPath> m_toAdd;
            private List<TfsPath> m_toEdit;

            internal Context() {
                m_toAdd = new List<TfsPath>();
                m_toEdit = new List<TfsPath>();
            }

            internal event Action OnComplete;

            internal void Execute() {
                ProcessItems(m_toAdd, Add);
                ProcessItems(m_toEdit, Checkout);
                if (OnComplete != null)
                    OnComplete();
            }

            internal void EnqueueAdd(TfsPath path) {
                m_toAdd.Add(path);
            }
            internal void EnqueueEdit(TfsPath path) {
                m_toEdit.Add(path);
            }
        }

        internal static TfsStatus Activate(TfsPath path) {
            return Activate(path, null);
        }
        internal static TfsStatus Activate(TfsPath path, TfsPath sourcePath) {
            var status = new TfsStatus(path.Workspace, path.Url, sourcePath);
            return status;
        }
        internal static IEnumerable<TfsStatus> Activate(TfsCollection collection, Queue<string> queue) {
            var result = new List<TfsStatus>();
            while (!Regex.IsMatch(queue.Peek(), @"\d* change[(]s[)]")) {
                var value = queue.Dequeue();
                if (!value.StartsWith("$"))
                    throw new ArgumentException();

                value = Regex.Replace(value, @";C\d*$", "");
                var url = collection.GetUrl(value);
                if (Directory.Exists(collection.Workspace.GetPath(url)))
                    url = url.NormalizeUrlDirectory();

                value = GetValue(queue.Dequeue());
                var user = collection.Loader.GetUser(value);

                value = GetValue(queue.Dequeue());
                var date = DateTime.ParseExact(value, "dddd, MMMM dd, yyyy h:mm:ss tt", CultureInfo.InvariantCulture);

                value = GetValue(queue.Dequeue());
                var lk = value == "none" ? TfsLock.None : value == "check-out" ? TfsLock.Checkout : TfsLock.Checkin;

                value = GetValue(queue.Dequeue());
                var change =
                   (from o in value.StringSplit(",", " ")
                    join e in Enum.GetValues(typeof(TfsChange)).Cast<TfsChange>() on o equals e.ToString().ToLower()
                    select e).Aggregate(default(TfsChange), (a, o) => a | o);

                var workspace = collection.GetWorkspace(GetValue(queue.Dequeue()), user.Name);

                TfsPath sourcePath = null;
                if (queue.Peek().Trim().StartsWith("Source item"))
                    sourcePath = collection.GetItem(GetValue(queue.Dequeue()));

                var localItem = GetValue(queue.Dequeue());

                if (queue.Peek().Trim().StartsWith("File type"))
                    GetValue(queue.Dequeue());

                var status = new TfsStatus(workspace, url, sourcePath);
                status.Change = change;
                status.Lock = lk;
                status.DateTime = date;

                result.Add(status);
            }

            collection.CacheItemOrFolder(
                from o in result
                where o.m_change.IsSet(TfsChange.Delete) && o.Workspace == collection.Workspace
                select collection.GetServerPath(o.m_url));

            return result;
        }
        internal static void Update(IEnumerable<TfsStatus> status) {
            var context = new Context();
            foreach (var o in status)
                o.Update(context);
            context.Execute();
        }

        private static string GetValue(string line) {
            var colon = line.IndexOf(':');
            return line.Substring(colon + 1).Trim();
        }

        private Uri m_url;
        private TfsPath m_path;
        private DateTime m_date;
        private TfsLock m_lock;
        private TfsChange m_change;
        private TfsWorkspace m_workspace;
        private TfsPath m_sourcePath;

        private TfsStatus(TfsWorkspace workspace, Uri url, TfsPath sourcePath) {
            m_url = url;
            m_workspace = workspace;
            m_sourcePath = sourcePath;
            m_date = DateTime.Now;
        }

        private void Update(Context context) {
            if (!Path.IsDirty)
                return;

            if (IsNew) {
                Path.Update();
                context.EnqueueAdd(Path);
                m_change |= TfsChange.Add;

            } else {

                if (!IsEditable) {
                    context.EnqueueEdit(Path);
                    m_change |= TfsChange.Edit;
                }

                var item = Item;
                context.OnComplete += delegate() { item.Update(); };
            }

            Path.IsDirty = false;
        }
        private void Initialize() {
            if (m_path == null) {
                m_path = Collection.GetItemOrFolder(m_url);

                var parent = m_path.Parent;
                if (parent != null && Workspace.GetStatus(parent).IsDeleted)
                    m_change |= TfsChange.Delete;

                if (!IsDeleted && m_path.IsFile) {
                    // if the file is missing or present with the readonly bit cleared then mark it as a new file
                    if (!File.Exists(m_path.Path) ||
                        (m_change == TfsChange.None &&
                        !File.GetAttributes(m_path.Path).IsSet(FileAttributes.ReadOnly)))
                        m_change = TfsChange.New;
                }
            }
        }

        internal void Delete() {
            foreach (var o in Path.Cache<TfsStatus>())
                o.m_change |= TfsChange.Delete;

            if (Item != null)
                Item.IsDirty = false;
        }
        internal void Branch(TfsPath sourcePath) {
            m_change |= TfsChange.Branch;
            m_sourcePath = sourcePath;
        }

        public TfsPath Path {
            get {
                Initialize();
                return m_path;
            }
        }
        public TfsPath SourcePath {
            get { return m_sourcePath; }
        }
        public TfsWorkspace Workspace {
            get { return m_workspace; }
        }

        public TfsItem Item {
            get { return Path as TfsItem; }
        }
        public TfsCollection Collection {
            get { return Workspace.Collection; }
        }
        public TfsLoader Loader {
            get { return Collection.Loader; }
        }

        public DateTime DateTime {
            get {
                return m_date;
            }
            internal set { m_date = value; }
        }

        public TfsLock Lock {
            get {
                return m_lock;
            }
            private set { m_lock = value; }
        }
        public bool IsLocked {
            get { return m_lock != TfsLock.None; }
        }
        public bool IsCheckOutLocked {
            get { return m_lock == TfsLock.Checkout; }
        }
        public bool IsCheckInLocked {
            get { return m_lock == TfsLock.Checkin; }
        }

        public TfsChange Change {
            get {
                Initialize();
                return m_change;
            }
            private set { m_change = value; }
        }
        public bool IsNominal {
            get { return Change == TfsChange.None; }
        }
        public bool IsEditable {
            get { return IsAdded || IsCheckedOut; }
        }
        public bool IsNew {
            get { return Change == TfsChange.New; }
        }
        public bool IsAdded {
            get { return Change.IsSet(TfsChange.Add); }
        }
        public bool IsBranched {
            get { return Change.IsSet(TfsChange.Branch); }
        }
        public bool IsDeleted {
            get { return Change.IsSet(TfsChange.Delete); }
        }
        public bool IsCheckedOut {
            get { return Change.IsSet(TfsChange.Edit); }
        }
        public bool IsMerged {
            get { return Change.IsSet(TfsChange.Merge); }
        }
        public bool IsRenamed {
            get { return Change.IsSet(TfsChange.Rename); }
        }

        public override string ToString() {
            return Change + ": " + Path.Path + " " + Workspace.ToString();
        }
    }

    [Flags]
    internal enum StatusCacheFlags {
        LocalStatus = 1 << 0,
        GlobalStatus = 1 << 1 | LocalStatus,
    }
    public class TfsCollection : TfsFolder {

        public static string FindLabFile(string dir) {
            dir = dir.NormalizeDirectory();

            // discover lab root
            var dirUrl = new Uri(dir);
            while (dirUrl != null && !File.Exists(dirUrl.LocalPath + ".lab"))
                dirUrl = dirUrl.GetParentDirectory();

            if (dirUrl == null)
                return null;

            return dirUrl.LocalPath;
        }

        private TfsLoader m_loader;
        private Dictionary<string, TfsWorkspace> m_workspacesByKey;
        private Dictionary<Uri, TfsFolder> m_foldersByUrl;
        private TfsFolder m_labFolder;
        private StatusCacheFlags m_statusFlags;

        internal Dictionary<Uri, TfsPath> _m_paths;

        internal TfsCollection(TfsLoader loader, Uri url)
            : base(null, url) {
            m_loader = loader;
            m_foldersByUrl = new Dictionary<Uri, TfsFolder>(UriComparer.IgnoreCase);
            m_foldersByUrl[url] = this;
        }

        internal void CacheStatus(StatusCacheFlags flags) {

            if (flags == 0)
                throw new ArgumentException();

            if ((m_statusFlags & flags) == flags)
                return;

            m_statusFlags = flags;

            var globalStatus = flags == StatusCacheFlags.GlobalStatus;
            var tfStatus = new TfStatus() {
                User = globalStatus ? "*" : null,
                WorkingDirectory = Collection.LabFolder.Path,
                Itemspec = ".",
                Format = TfFormat.Detailed,
                Recursive = true,
            }.ReadToEnd();

            var queue = tfStatus.ToLinesQueue();
            if (queue.Peek() == "There are no pending changes.")
                return;

            var result = TfsStatus.Activate(Collection, queue);

            foreach (var workspace in result.Select(o => o.Workspace).Distinct())
                workspace.ClearStatus();

            foreach (var status in result) {
                status.Workspace.CacheStatus(status);

                // remove non-local paths (new items on remote clients) from folder caches
                var addition = status.IsAdded || status.IsBranched || status.IsRenamed;
                if (addition && status.Workspace != Collection.Workspace)
                    status.Item.Folder.UncahePath(status.Item);
            }
        }
        internal TfsPath GetItemOrFolder(Uri url) {
            if (url.IsDirectory())
                return GetFolder(url);

            var folder = m_foldersByUrl.GetValueOrDefault(url.NormalizeUrlDirectory());
            if (folder != null)
                return folder;

            return GetPath(url);
        }
        internal void CacheItemOrFolder(IEnumerable<string> paths) {
            if (paths.None())
                return;

            var str = new TfDir() {
                Itemspec = new TextJoin(" ") { paths },
            }.ReadToEnd();

            CachePaths(new StreamReader(str.ToStream()));
        }

        public TfsWorkspace GetWorkspace(string name, string owner) {
            if (m_workspacesByKey == null)
                m_workspacesByKey = new Dictionary<string, TfsWorkspace>();
            var key = TfsWorkspace.GetKey(name, owner);
            var workspace = m_workspacesByKey.GetValueOrDefault(key);
            if (workspace == null)
                m_workspacesByKey[key] = workspace = new TfsWorkspace(this, name, Loader.GetUser(owner));
            return workspace;
        }
        public TfsFolder LabFolder {
            get {
                if (m_labFolder == null) {
                    var currentFolder = Workspace.CurrentFolder;
                    var dirUrl = currentFolder.PathUrl;
                    while (dirUrl != null && !File.Exists(dirUrl.LocalPath + ".lab"))
                        dirUrl = dirUrl.GetParentDirectory();

                    if (dirUrl == null)
                        throw new ArgumentException("Could not determine lab folder because " +
                            "a .lab file was not found in the parents of the working directory " + currentFolder.Path);

                    m_labFolder = Collection.GetFolder(dirUrl.LocalPath);
                }
                return m_labFolder;
            }
        }
        public override TfsFolder GetFolder(Uri relUrl) {
            var url = relUrl;

            // this shouldn't be necessary but apparently there is a bug in the url comparison code
            if (!url.IsAbsoluteUri)
                url = new Uri(Url, relUrl);

            if (!url.IsDirectory())
                throw new ArgumentException();

            var folder = m_foldersByUrl.GetValueOrDefault(url);
            if (folder == null)
                folder = m_foldersByUrl[url] = new TfsFolder(this, url);

            return folder;
        }
        public IEnumerable<TfsStatus> Status() {
            return Workspaces().SelectMany(o => o.Status());
        }
        public IEnumerable<TfsWorkspace> Workspaces() {
            CacheStatus(StatusCacheFlags.GlobalStatus);
            return m_workspacesByKey.Values;
        }

        public new Uri GetUrl(string serverOrFilePath) {
            if (!serverOrFilePath.StartsWith("$"))
                return base.GetUrl(serverOrFilePath);

            var result = new Uri(Url, serverOrFilePath.TrimStart('$', '/').Replace("#", "%23"));
            return result;
        }
        public string GetServerPath(Uri url) {
            var result = "$/" + Url.MakeRelativeUri(url).ToString();
            return result;
        }
        public override TfsLoader Loader {
            get { return m_loader; }
        }
    }
    public class TfsUser {

        private TfsLoader m_loader;
        private string m_name;

        internal TfsUser(TfsLoader loader, string name) {
            m_loader = loader;
            m_name = name;
        }

        public TfsLoader Loader {
            get { return m_loader; }
        }
        public string Name {
            get { return m_name; }
        }

        public override string ToString() {
            return m_name;
        }
    }
    public class TfsWorkspace {

        internal static string GetKey(string name, string owner) {
            return name + ";" + owner;
        }

        private string m_name;
        private TfsUser m_owner;
        private TfsCollection m_collection;
        private IEnumerable<TfsMapping> m_mappings;
        private Dictionary<Uri, TfsStatus> m_statusByUrl;

        internal TfsWorkspace(TfsCollection collection, string name, TfsUser owner) {
            m_collection = collection;
            m_name = name;
            m_owner = owner;
        }

        internal void CacheMappings(IEnumerable<TfsMapping> mappings) {
            m_mappings = mappings.ToList();
        }
        internal void ClearStatus() {
            m_statusByUrl = new Dictionary<Uri, TfsStatus>(UriComparer.IgnoreCase);
        }
        internal void CacheStatus(TfsStatus status) {
            m_statusByUrl[status.Path.Url] = status;
        }
        internal TfsStatus GetStatus(TfsPath path) {
            var status = GetCachedStatus(path);
            if (status == null)
                CacheStatus(status = TfsStatus.Activate(path));
            return status;
        }
        internal TfsStatus GetCachedStatus(TfsPath path) {
            if (m_statusByUrl == null)
                Status();

            var url = path.Url;
            var status = m_statusByUrl.GetValueOrDefault(url);
            return status;
        }

        public TfsFolder CurrentFolder {
            get {
                var currentUrl = GetUrl(SysEnv.CurrentDirectory.NormalizeDirectory());
                var currentFolder = m_collection.GetFolder(currentUrl);
                return currentFolder;
            }
        }
        public string Name {
            get { return m_name; }
        }
        public TfsUser Owner {
            get { return m_owner; }
        }
        public TfsLoader Loader {
            get { return Collection.Loader; }
        }
        public TfsCollection Collection {
            get { return m_collection; }
        }
        public IEnumerable<TfsMapping> Mappings() {
            return m_mappings;
        }
        public IEnumerable<TfsStatus> Status() {
            var flags = IsLocalWorkspace ? StatusCacheFlags.LocalStatus : StatusCacheFlags.GlobalStatus;
            Collection.CacheStatus(flags);
            if (m_statusByUrl == null)
                m_statusByUrl = new Dictionary<Uri, TfsStatus>();
            return m_statusByUrl.Values.Where(o => !o.IsNominal);
        }

        public bool IsLocalWorkspace {
            get { return this == Collection.Workspace; }
        }
        public string GetPath(Uri url) {
            return GetPathUrl(url).LocalPath;
        }
        public Uri GetPathUrl(Uri url) {
            var mappings =
                from o in Mappings()
                orderby o.Url.AbsoluteUri.Length
                where url.IsSubUrlOrEqual(o.Url)
                select o;
            var mapping = mappings.FirstOrDefault();
            if (mapping == null || mapping.IsCloaked)
                return null;

            var pathUri = new Uri(mapping.PathUrl, mapping.Url.MakeRelativeUriPath(url));

            return pathUri;
        }
        public Uri GetUrl(Uri path) {
            var mappings =
                from o in Mappings()
                orderby o.Url.AbsoluteUri.Length
                where path.IsSubUrlOrEqual(o.PathUrl)
                select o;
            var mapping = mappings.FirstOrDefault();
            if (mapping == null || mapping.IsCloaked)
                return null;

            var url = new Uri(mapping.Url, mapping.PathUrl.MakeRelativeUriPath(path));

            return url;
        }
        public Uri GetUrl(string path) {
            return GetUrl(new Uri(path));
        }

        public override string ToString() {
            return m_name + ";" + m_owner;
        }
    }
    public class TfsMapping {

        internal static TfsMapping Parse(TfsWorkspace workspace, string line) {
            var match = Regex.Match(line, " (?<cloaked>[(]cloaked[)] )?(?<server>[^:]*):( (?<client>.*$))?");
            var cloaked = match.Groups["cloaked"].Success;
            var serverPath = match.Groups["server"].Value;
            var path = match.Groups["client"].Value;
            return new TfsMapping(workspace, cloaked, serverPath, path);
        }

        private TfsWorkspace m_workspace;
        private bool m_cloaked;
        private Uri m_pathUrl;
        private Uri m_url;

        private TfsMapping(TfsWorkspace workspace, bool cloaked, string serverPath, string path) {
            m_workspace = workspace;
            m_cloaked = cloaked;
            m_url = workspace.Collection.GetUrl(serverPath).NormalizeUrlDirectory();
            m_pathUrl = string.IsNullOrEmpty(path) ? null : new Uri(path.NormalizeDirectory());
        }

        public TfsLoader Loader {
            get { return Collection.Loader; }
        }
        public TfsCollection Collection {
            get { return Workspace.Collection; }
        }
        public TfsWorkspace Workspace {
            get { return m_workspace; }
        }
        public bool IsCloaked {
            get { return m_cloaked; }
        }
        public Uri Url {
            get { return m_url; }
        }
        public Uri PathUrl {
            get { return m_pathUrl; }
        }

        public override string ToString() {
            if (IsCloaked)
                return "(cloaked) " + m_url;

            return m_url + " -> " + m_pathUrl.LocalPath;
        }
    }

    public abstract class TfsPath {

        private Uri m_pathUri;
        private Uri m_url;
        private TfsCollection m_collection;
        private bool m_isDirty;

        protected TfsPath(TfsCollection collection, Uri url) {
            m_collection = collection;
            if (m_collection == null)
                m_collection = (TfsCollection)this;
            m_url = url;

            if (m_collection._m_paths == null)
                m_collection._m_paths = new Dictionary<Uri, TfsPath>(UriComparer.IgnoreCase);
            var _item = m_collection._m_paths.GetValueOrDefault(url);
            if (_item != null)
                throw new ArgumentException("Assert Failed: Path already created: " + url);
            m_collection._m_paths[url] = this;
        }

        internal abstract IEnumerable<TfsPath> Cache();
        internal IEnumerable<T> Cache<T>() {
            if (typeof(T) == typeof(TfsStatus))
                return Cache().Select(o => o.GetStatus()).Cast<T>();

            return Cache().WhereIs<T>();
        }
        internal virtual void Update() {
            throw new InvalidOperationException();
        }

        #region Identity
        public TfsInfo Info {
            get { return TfsLoader.GetInfo(GetType()); }
        }
        public Uri GetUrl(string path) {
            if (path.StartsWith("$"))
                return Collection.GetUrl(path);
            if (path == null)
                return Url;
            var relUrl = new Uri(path, UriKind.RelativeOrAbsolute);
            if (relUrl.IsAbsoluteUri && relUrl.IsFile)
                return Workspace.GetUrl(path);
            var url = new Uri(Url, path.RelativePathToRelativeUrl());
            return url;
        }
        public string Name {
            get { return Url.GetName(); }
        }
        public string NameWithoutExtensions {
            get {
                var nameWithNoExtensions = Regex.Match(Name, @"^[^\.]*").Value;
                return nameWithNoExtensions;
            }
        }
        public virtual TfsLoader Loader {
            get { return m_collection.Loader; }
        }
        public TfsWorkspace Workspace {
            get { return m_collection.Loader.Workspace; }
        }
        public TfsCollection Collection {
            get { return m_collection; }
        }
        public string ServerPath {
            get { return Collection.GetServerPath(Url); }
        }
        public string Path {
            get {
                if (PathUrl == null)
                    return null;
                return PathUrl.LocalPath;
            }
        }
        public Uri PathUrl {
            get {
                if (m_pathUri == null)
                    m_pathUri = Workspace.GetPathUrl(Url);
                return m_pathUri;
            }
        }
        public Uri Url {
            get { return m_url; }
        }
        public TfsFolder Parent {
            get {
                if (IsFile)
                    return Folder;
                if (this is TfsCollection)
                    return null;
                return m_collection.GetFolder(Url.GetParentDirectory());
            }
        }
        public TfsFolder Folder {
            get { return m_collection.GetFolder(Url.GetDirectory()); }
        }
        public bool IsFolder {
            get { return this is TfsFolder; }
        }
        public bool IsFile {
            get { return !IsFolder; }
        }
        #endregion

        #region Get Multipule Items
        public abstract IEnumerable<TfsItem> GetItems(IEnumerable<string> extensions);
        public IEnumerable<T> Get<T>() {
            return Get<T>(typeof(T));
        }
        public IEnumerable<T> Get<T>(params Type[] types) {
            return Get<T>((IEnumerable<Type>)types);
        }
        public IEnumerable<T> Get<T>(IEnumerable<Type> types) {
            var infos =
               (from o in TfsLoader.Infos()
                from t in types
                where o.Type.IsSubclassOfOrIs(t) || o.Type.Implements(t)
                select o).ToList();

            var extensions =
                from o in infos
                where o.Extension != null
                select o.Extension;

            var result = GetItems(extensions).Cast<T>().ToArray();
            return result;
        }

        public T Get<T>(Uri relUrl) {
            var url = new Uri(Url, relUrl);
            var folder = Collection.GetFolder(url.GetDirectory());

            object result = folder;
            if (typeof(T) != typeof(TfsFolder) && !relUrl.IsDirectory())
                result = folder.GetItem(url.GetName());

            return (T)(object)result;
        }
        public T Get<T>(string path) {
            return Get<T>(GetUrl(path));
        }
        #endregion

        #region Get Single Item
        public TfsPath GetPath(Uri relUrl) {
            return Get<TfsPath>(relUrl);
        }
        public TfsItem GetItem(Uri relUrl) {
            return Get<TfsItem>(relUrl);
        }
        public virtual TfsFolder GetFolder(Uri relUrl) {
            return Get<TfsFolder>(relUrl);
        }
        public TfsProject GetProject(Uri relUrl) {
            return Get<TfsProject>(relUrl);
        }

        public TfsPath GetPath(string path) {
            return Get<TfsPath>(path);
        }
        public virtual TfsItem GetItem(string path) {
            return Get<TfsItem>(path);
        }
        public TfsFolder GetFolder(string path) {
            return Get<TfsFolder>(path);
        }
        public TfsProject GetProject(string path) {
            return Get<TfsProject>(path);
        }
        #endregion

        #region Status
        public TfsStatus GetStatus() {
            return Workspace.GetStatus(this);
        }
        public IEnumerable<TfsStatus> StatusElsewhere() {
            return Collection.Workspaces()
                .Where(o => o != Workspace)
                .Select(o => o.GetCachedStatus(this))
                .WhereNotDefault();
        }

        public TfsLock Lock {
            get { return Workspace.GetStatus(this).Lock; }
        }
        public bool IsLocked {
            get { return Lock != TfsLock.None; }
        }
        public bool IsCheckOutLocked {
            get { return Lock == TfsLock.Checkout; }
        }
        public bool IsCheckInLocked {
            get { return Lock == TfsLock.Checkin; }
        }

        public TfsChange Change {
            get { return Workspace.GetStatus(this).Change; }
        }
        public bool IsNominal {
            get { return Change == TfsChange.None; }
        }
        public bool IsNew {
            get { return Change == TfsChange.New; }
        }
        public bool IsAdded {
            get { return Change.IsSet(TfsChange.Add); }
        }
        public bool IsBranched {
            get { return Change.IsSet(TfsChange.Branch); }
        }
        public bool IsDeleted {
            get { return Change.IsSet(TfsChange.Delete); }
        }
        public bool IsEditable {
            get { return Change.IsSet(TfsChange.Edit); }
        }
        public bool IsMerged {
            get { return Change.IsSet(TfsChange.Merge); }
        }
        public bool IsRenamed {
            get { return Change.IsSet(TfsChange.Rename); }
        }

        public void Touch() {
            m_isDirty = true;
        }
        public bool IsDirty {
            get { return m_isDirty; }
            internal set { m_isDirty = value; }
        }
        #endregion

        #region Mutations
        public virtual IEnumerable<TfsItem> DependentItems() {
            var result =
               (from o in Info.DependentItems()
                let path = o.Replace("*.", NameWithoutExtensions + ".")
                where File.Exists(path)
                select GetItem(path)).ToList();
            return result;
        }
        public IEnumerable<TfsPath> Branch(string target) {
            return BranchOrRename(target, true).ToList();
        }
        public IEnumerable<TfsPath> Rename(string target) {
            return BranchOrRename(target, false).ToList();
        }

        private IEnumerable<TfsPath> BranchOrRename(string target, bool isBranch) {
            if (IsFolder)
                target = target.NormalizeDirectory();
            var targetPath = GetPath(target);

            var workingDirectoryUrl = targetPath.PathUrl.CommonParent(PathUrl);
            var workingDirectory = workingDirectoryUrl.LocalPath;

            // branch
            string output = null;
            if (isBranch) {
                output = new TfBranch() {
                    WorkingDirectory = workingDirectory,
                    OldItem = workingDirectoryUrl.MakeRelativePath(PathUrl),
                    NewItem = workingDirectoryUrl.MakeRelativePath(targetPath.PathUrl),
                    NoPrompt = true,
                }.ReadToEnd();

                // rename
            } else {
                Scorch();

                output = new TfRename() {
                    WorkingDirectory = workingDirectory,
                    OldItem = workingDirectoryUrl.MakeRelativePath(PathUrl),
                    NewItem = workingDirectoryUrl.MakeRelativePath(targetPath.PathUrl),
                }.ReadToEnd();

                GetStatus().Delete();
            }

            // set sourcePaths
            var paths = UpdateSourcePaths(target, workingDirectoryUrl.LocalPath, output);

            // fix references
            foreach (var node in Collection.LabFolder.Get<TfsNode>())
                node.FixReferences(PathUrl, new Uri(target));

            // branch or rename dependent paths
            foreach (var dependentItem in DependentItems()) {

                // only items (not folders) can have dependent paths
                var branchedItem = (TfsItem)paths.Single();

                // the dependent item should be renamed just like the parent item was renamed
                var targetName = dependentItem.Name.Replace(NameWithoutExtensions + ".", branchedItem.NameWithoutExtensions + ".");

                // the newly named dependent item should live where the new item lives
                var targetFullname = branchedItem.Folder.Path + targetName;

                foreach (var path in dependentItem.BranchOrRename(targetFullname, isBranch))
                    yield return path;
            }

            foreach (var path in paths)
                yield return path;
        }
        private IEnumerable<TfsPath> UpdateSourcePaths(string target, string workingDirectory, string branchOrRenameOutput) {
            var targetUrl = GetUrl(target);

            var root = GetFolder(workingDirectory);
            var queue = branchOrRenameOutput.ToLinesQueue();

            // parse the paths that were branched
            var uris = new List<Uri>();
            var folder = root;
            while (queue.Any()) {
                var line = queue.Dequeue();
                if (line.EndsWith(":"))
                    folder = root.GetFolder(Regex.Match(line, "^(?<path>.*):$").Get("path").NormalizeDirectory());
                else
                    uris.Add(folder.GetUrl(line));
            }

            // provide source urls to branched items and folders
            var result = new List<TfsPath>();
            if (IsFile) {
                var item = GetItem(uris.Single());
                item.GetStatus().Branch(this);
                result.Add(item);

            } else {
                foreach (var uri in uris) {
                    var targetPath = Collection.GetItemOrFolder(uri);
                    var sourcePath = GetPath(new Uri(Url, targetUrl.MakeRelativeUri(targetPath.Url)));
                    targetPath.GetStatus().Branch(sourcePath);
                    result.Add(targetPath);
                }
            }

            return result;
        }
        public void Delete() {
            Scorch();

            var deleteSource = new TfDelete() {
                Itemspec = Path,
                Recursive = true,
            }.ReadToEnd();

            GetStatus().Delete();
        }
        public void Scorch() {
            if (!IsFolder)
                return;

            new TfptScorch() {
                WorkingDirectory = Path,
                Itemspec = ".",
                Recursive = true,
                NoPrompt = true,
            }.ReadToEnd();
        }
        #endregion

        public override string ToString() {
            return m_url.AbsoluteUri;
        }
    }
    public class TfsFolder : TfsPath {

        private Dictionary<Uri, TfsPath> m_paths;
        private HashSet<string> m_extensions;
        private bool m_everythingCached;

        internal TfsFolder(TfsCollection collection, Uri url)
            : base(collection, url) {

            m_paths = new Dictionary<Uri, TfsPath>(UriComparer.IgnoreCase);
            m_extensions = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
            if (this is TfsCollection)
                return;

            Parent.CachePath(this);
            m_extensions.AddRange(Parent.m_extensions);
        }

        protected void CachePaths(TextReader stream) {

            while (stream.Peek() != -1) {
                var line = stream.ReadLine();

                var serverPathRegex = Regex.Match(line, "^[$]/(?<serverPath>.*):$");
                if (serverPathRegex.Success) {

                    // resolve folder
                    var serverPath = serverPathRegex.Groups["serverPath"].Value;
                    var folderUrl = Collection.GetUrl(serverPath).NormalizeUrlDirectory();

                    // activate item types not already cached in folder
                    string path = null;
                    while (!string.IsNullOrEmpty(path = stream.ReadLine())) {
                        if (path.StartsWith("$"))
                            path = path.TrimStart('$') + "\\";
                        GetPath(new Uri(folderUrl, path));
                    }
                }
            }
        }

        internal override IEnumerable<TfsPath> Cache() {
            yield return this;
            foreach (var path in m_paths.Values.SelectMany(o => o.Cache()))
                yield return path;
        }
        internal void CachePath(TfsPath path) {
            m_paths.Add(path.Url, path);
        }
        internal void UncahePath(TfsPath path) {
            m_paths.Remove(path.Url);
        }

        public void VerifyProjectGraph() {
            var result = LoadProjectGraph().Cast<object>().ToQueue();
            var traversed = result.ToHashSet();

            while (result.Any()) {
                var o = result.Dequeue();

                foreach (var m in o.GetType().Get<MethodInfo>(isInstance: true, isPublic: true)) {
                    if (m.ReturnType == typeof(void) ||
                        m.GetParameters().Any() ||
                        m.ContainsGenericParameters ||
                        m.DeclaringType.ContainsGenericParameters)
                        continue;

                    var x = m.Invoke(o, null);
                    if (x == null)
                        continue;

                    var e = x as IEnumerable;
                    if (e != null)
                        e.Cast<object>().ToArray();

                    var type = x.GetType();
                    if (type.IsClass && !type.HasElementType &&
                        x.GetType().Assembly == Assembly.GetExecutingAssembly() &&
                        !traversed.Contains(x)) {

                        traversed.Add(x);
                        result.Enqueue(x);
                    }
                }
            }
        }
        public IEnumerable<TfsItem> LoadProjectGraph() {
            var collection = Collection;
            var projectExtensions =
                from o in TfsLoader.Infos()
                where o.IsProject
                select o.Extension;

            var extensions = projectExtensions.Concat(".sln");

            return GetItems(extensions).ToArray();
        }
        public void WriteChanges() {
            TfsStatus.Update(Cache().Select(o => o.GetStatus()));
        }

        public IEnumerable<TfsItem> Items() {
            return Paths().WhereIs<TfsItem>();
        }
        public IEnumerable<TfsPath> Paths() {
            if (!m_everythingCached) {

                var extensions = TfsLoader.Infos().Select(o => o.Extension).ToList();
                m_extensions.AddRange(extensions);

                // remember which extensions have been cached for this folder and its children
                foreach (var folder in Cache<TfsFolder>()) {
                    folder.m_everythingCached = true;
                    folder.m_extensions.AddRange(extensions);
                }

                var stream = new TfDir() {
                    Itemspec = "*",
                    Recursive = true,
                }.ToTextReader();

                CachePaths(stream);
            }

            return Cache<TfsPath>();
        }
        public override IEnumerable<TfsItem> GetItems(IEnumerable<string> extensions) {
            var uncachedExtensions = extensions.Except(m_extensions).ToArray();

            if (uncachedExtensions.Any()) {

                // remember which extensions have been cached for this folder and its children
                foreach (var folder in Cache<TfsFolder>())
                    folder.m_extensions.AddRange(uncachedExtensions);

                var stream = new TfDir() {
                    Itemspec = new TextJoin(" ") { 
                        uncachedExtensions.Select(o => ServerPath + "*" + o) 
                    },
                    Recursive = true,
                }.ToTextReader();

                CachePaths(stream);
            }

            var extensionsHash = new HashSet<string>(extensions, StringComparer.InvariantCultureIgnoreCase);
            return Cache<TfsItem>().Where(o => extensionsHash.Contains(o.Extension) && !o.IsDeleted);
        }
        public override TfsItem GetItem(string relPath) {
            if (Regex.IsMatch(relPath, @"[\]|[/]"))
                return base.GetItem(relPath);

            if (m_paths == null)
                m_paths = new Dictionary<Uri, TfsPath>(UriComparer.IgnoreCase);

            var url = new Uri(Url, relPath);
            var result = m_paths.GetValueOrDefault(url);
            if (result == null)
                result = m_paths[url] = TfsItem.Activate(Collection, url);

            var item = (TfsItem)result;
            return item;
        }

        public TfsSolution NewSolution(string name, int year, string defaultUnmanagedPlatform) {
            var solution = Get<TfsSolution>(name);
            if (solution != null)
                solution.Clear(year, defaultUnmanagedPlatform);
            else
                solution = new TfsSolution(this, name, year, defaultUnmanagedPlatform);
            return solution;
        }
    }
    public class TfsItem : TfsPath {

        internal static TfsItem Activate(TfsCollection collection, Uri url) {
            var extension = SysPath.GetExtension(url.Segments.Last());
            var info = TfsLoader.GetInfo(extension);
            var type = info == null ? typeof(TfsItem) : info.Type;

            return (TfsItem)type.Activate(collection, url);
        }

        internal TfsItem(TfsCollection collection, Uri url)
            : base(collection, url) {

            if (!url.IsDirectory())
                Folder.CachePath(this);
        }

        internal override IEnumerable<TfsPath> Cache() {
            yield return this;
        }

        public string Extension {
            get { return SysPath.GetExtension(Url.Segments.Last()).ToLower(); }
        }
        public string ReadToEnd() {
            return new StreamReader(GetStream()).ReadToEnd();
        }
        public virtual Stream GetStream() {
            var workspace = Workspace;
            if (workspace == null)
                throw new NotImplementedException();
            return new FileStream(Path, FileMode.Open, FileAccess.Read);
        }
        public override TfsItem GetItem(string relPath) {
            return Folder.GetItem(relPath);
        }
        public override IEnumerable<TfsItem> GetItems(IEnumerable<string> extensions) {
            return Folder.GetItems(extensions).Cast<TfsItem>();
        }
    }
    public abstract class TfsNode : TfsItem {

        internal TfsNode(TfsCollection collection, Uri url)
            : base(collection, url) {
        }

        private IEnumerable<XObject> GetXObjects() {
            foreach (var o in Xml.Descendants()) {
                yield return o;
                foreach (var a in o.Attributes())
                    yield return a;
            }
        }
        internal virtual IEnumerable<XObject> PathXmlReferences() {

            var attributes = Info.PathAttributes();
            var elements = Info.PathElements();
            var itemGroups = Info.PathIncludes();

            var result =
                from o in GetXObjects()
                let name = o.GetLocalName()
                let isElement = o is XElement && elements.Contains(name)
                let isInclude = o is XElement && itemGroups.Contains(name)
                let isAttribute = o is XAttribute && attributes.Contains(name)
                where isInclude || isElement || isAttribute
                select (isElement || isAttribute) ? o : ((XElement)o).Attribute("Include");

            return result;
        }
        internal void FixReferences(Uri sourceUrl, Uri targetUrl) {

            if (IsDeleted)
                throw new InvalidOperationException();

            if (PathUrl.IsSubUrlOrEqual(sourceUrl))
                return;

            var thisIsBranched = PathUrl.IsSubUrlOrEqual(targetUrl);
            var sourceNode = this;
            if (thisIsBranched)
                sourceNode = (TfsNode)GetStatus().SourcePath;

            foreach (var xml in PathXmlReferences()) {

                var currentRelPath = xml.GetValue();
                var currentEdge = new Uri(sourceNode.PathUrl, currentRelPath);

                // shouldn't have absolute paths anywhere but when they are found ignore them
                var currentRelPathUri = new Uri(currentRelPath, UriKind.RelativeOrAbsolute);
                if (currentRelPathUri.IsAbsoluteUri)
                    continue;

                var newEdge = currentEdge;
                var itemIsBranched = currentEdge.IsSubUrlOrEqual(sourceUrl);
                if (itemIsBranched)
                    newEdge = new Uri(targetUrl, sourceUrl.MakeRelativeUri(currentEdge));
                var newRelPath = PathUrl.MakeRelativePath(newEdge);

                if (currentRelPath.ToLower() != newRelPath.ToLower())
                    xml.SetValue(newRelPath);
            }
        }

        public abstract XElement Xml { [XoXml]get; }
    }

    public class TfsAttributeAttribute : Attribute {
        public string m_xpath;
        public TfsAttributeAttribute() {
            m_xpath = string.Empty;
        }
        public TfsAttributeAttribute(string xpath) {
            m_xpath = xpath;
        }
        public string XPath {
            get { return m_xpath.NormalizeUrlDirectory(); }
        }
    }

    public interface ITfsConfigurable {
        IEnumerable<TfsConfiguration> Configurations();
        void RemoveConfiguration(TfsConfiguration configuration);
        TfsConfiguration DefaultConfiguration { get; set; }
    }

    [XoType(Namespace)]
    public abstract class TfsProject : TfsNode, ITfsSolutionProject, ITfsConfigurable {

        public const string Namespace = "http://schemas.microsoft.com/developer/msbuild/2003";
        public static XNamespace XNamespace = (XNamespace)Namespace;
        private Dictionary<TfsConfiguration, TfsProjectConfiguration> m_configurations;

        private XElement m_xml;

        internal TfsProject(TfsCollection collection, Uri url)
            : base(collection, url) {
        }

        internal override void Update() {
            m_xml.Save(Path);
        }

        protected virtual IEnumerable<TfsProjectConfiguration> GetConfigurations() {
            return Enumerable.Empty<TfsProjectConfiguration>();
        }

        public override XElement Xml {
            get {
                if (m_xml == null) {
                    using (var stream = new StreamReader(GetStream()))
                        m_xml = XElement.Load(stream);
                    m_xml.Changed += delegate(object sender, XObjectChangeEventArgs e) { Touch(); };
                }
                return m_xml;
            }
        }

        [XoMember("//ProjectGuid")]
        public virtual Guid? Guid {
            get { return XoMember.GetValue<Guid?>(MethodInfo.GetCurrentMethod(), this); }
        }

        Guid ITfsSolutionObject.GetGuid(TfsSolution solution) {
            return (Guid)Guid;
        }

        [XoMember("//Configuration")]
        public string DefaultFlavor {
            get { return XoMember.GetValue(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        [XoMember("//Platform")]
        public string DefaultPlatform {
            get { return XoMember.GetValue(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        public TfsConfiguration DefaultConfiguration {
            get {
                if (DefaultFlavor == null && DefaultPlatform == null)
                    return null;
                return TfsConfiguration.Get(DefaultFlavor, DefaultPlatform);
            }
            set {
                DefaultFlavor = value.FlavorString;
                DefaultPlatform = value.PlatformString;
            }
        }

        public TfsProjectConfiguration GetProjectConfiguration(TfsConfiguration configuration) {
            if (m_configurations == null)
                ProjectConfigurations();
            return m_configurations.GetValueOrDefault(configuration);
        }
        public IEnumerable<TfsConfiguration> Configurations() {
            return ProjectConfigurations().Select(o => o.Configuration);
        }
        public IEnumerable<TfsProjectConfiguration> ProjectConfigurations() {
            if (m_configurations == null)
                m_configurations = GetConfigurations().ToDictionary(o => o.Configuration);
            return m_configurations.Values;
        }
        public void RemoveConfiguration(TfsConfiguration configuration) {
            var projectConfiguration = GetProjectConfiguration(configuration);
            if (projectConfiguration == null)
                throw new ArgumentException();
            projectConfiguration.Remove();
        }
    }

    [TfsObject(Extension = ".csproj", Guid = "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC",
        IsManagedProject = true, DefaultPlatform = TfsPlatform.AnyCpu, DependentFiles = "*.csproj.vspscc",
        PathIncludes = "Compile None ProjectReference Folder Content WebReferences AppDesigner EmbeddedResource WCFMetadata WCFMetadataStorage ApplicationDefinition Page",
        PathElements = "HintPath AssemblyOriginatorKeyFile")]
    public class TfsCsProject : TfsProject {

        internal static IEnumerable<XElement> GetConfigruationXml(XElement projectXml) {
            var xml =
                from o in projectXml.Elements(XNamespace + "PropertyGroup")
                from a in o.Attributes("Condition")
                where a.Value.Contains("'$(Configuration)|$(Platform)' ==")
                select o;
            return xml;
        }

        private IEnumerable<TfsItem> m_links;
        private IEnumerable<TfsProject> m_projectReferences;

        internal TfsCsProject(TfsCollection collection, Uri url)
            : base(collection, url) {
        }

        [XoMember(IsAttribute = true)]
        public double ToolsVersion {
            get { return XoMember.GetValue<double>(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        [XoMember]
        public string AssemblyName {
            get { return XoMember.GetValue(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        [XoMember]
        public string RootNamespace {
            get { return XoMember.GetValue(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        [XoMember]
        public string AssemblyOriginatorKeyFile {
            get { return XoMember.GetValue(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        [XoMember]
        public bool SignAssembly {
            get { return XoMember.GetValue<bool>(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue<bool>(MethodInfo.GetCurrentMethod(), this, value); }
        }
        [XoMember]
        public double TargetFrameworkVersion {
            get { return double.Parse((XoMember.GetValue(MethodInfo.GetCurrentMethod(), this)).TrimStart('v')); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, "v" + value.ToString("#.0")); }
        }
        [XoMember]
        public string PreBuildEvent {
            get { return XoMember.GetValue(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        [XoMember]
        public string PostBuildEvent {
            get { return XoMember.GetValue(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        [XoMember]
        public string ProjectTypeGuids {
            get { return XoMember.GetValue(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        public bool IsWebProject {
            get {
                var xml = Xml.Descendants(XNamespace + "ProjectTypeGuids").SingleOrDefault();
                if (xml == null)
                    return false;
                return xml.Value.CaseInsensitveEquals("{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}");
            }
        }

        public IEnumerable<TfsItem> Links() {
            if (m_links == null) {
                var items =
                    from o in Xml.GetNodes<XElement>(XNamespace, new Uri("xml://xpath//ItemGroup/*/Link"))
                    select GetItem((string)o.Parent.Attribute("Include"));

                m_links = items.ToList();
            }
            return m_links;
        }
        public IEnumerable<TfsProject> ProjectReferences() {
            if (m_projectReferences == null) {
                var xml = Xml.GetNodes<XElement>(XNamespace, new Uri("xml://xpath//ProjectReference"));
                var projectReferences = new List<TfsProject>();
                foreach (var o in xml) {
                    var relPath = (string)o.Attribute("Include");
                    var project = Get<TfsProject>(relPath);
                    if (project == null)
                        throw new ArgumentException(string.Format(
                            "Project {0} has a broken project reference to {1}",
                            Path, relPath));

                    var guid = (Guid)o.Element(XNamespace + "Project");
                    if (project.Guid != guid)
                        throw new ArgumentException(string.Format(
                            "Project {0}'s reference to project {1} uses the wrong GUID {2}. The GUID should be {3}.",
                            Path, project.Path, guid, project.Guid));

                    projectReferences.Add(project);
                }

                m_projectReferences = projectReferences;
            }

            return m_projectReferences;
        }

        protected override IEnumerable<TfsProjectConfiguration> GetConfigurations() {
            return GetConfigruationXml(Xml).Select(o => new TfsCsProjectConfiguration(this, o));
        }

        public new TfsCsProjectConfiguration GetConfiguration(TfsConfiguration configuration) {
            return (TfsCsProjectConfiguration)base.GetProjectConfiguration(configuration);
        }
        public new IEnumerable<TfsCsProjectConfiguration> Configurations() {
            return base.ProjectConfigurations().Cast<TfsCsProjectConfiguration>();
        }
    }
    public class TfsCsProjectConfiguration : TfsProjectConfiguration {

        internal static TfsConfiguration ParseConfiguration(XElement xml) {
            var regex = Regex.Match(xml.Attribute("Condition").Value, @"'[$][(]Configuration[)][|][$][(]Platform[)]'\s*==\s*'(?<config>[^']*)'");
            if (!regex.Success)
                throw new ArgumentException();
            return TfsConfiguration.Parse(regex.Get("config"));
        }

        private XElement m_xml;

        internal TfsCsProjectConfiguration(TfsCsProject project, XElement xml)
            : base(project, ParseConfiguration(xml)) {

            m_xml = xml;
        }

        internal override IEnumerable<XElement> Sections() {
            yield return m_xml;
        }
    }
    public enum TfsCSharpProjectType {
        None,
        Exe,
        Dll,
        WinExe,
    }

    [TfsObject(Extension = ".vcxproj", Guid = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942",
        IsUnmanagedProject = true, DependentFiles = "*.vcxproj.filters")]
    public class TfsVcxProject : TfsProject {

        internal TfsVcxProject(TfsCollection collection, Uri url)
            : base(collection, url) {
        }

        internal override IEnumerable<XObject> PathXmlReferences() {
            yield break;
        }
        protected override IEnumerable<TfsProjectConfiguration> GetConfigurations() {
            return
                from o in Xml.Elements(XNamespace + "ItemGroup")
                from a in o.Attributes("Label")
                where a.Value == "ProjectConfigurations"
                from xml in o.Elements()
                select new TfsVcxProjectConfiguration(this, xml);
        }

        [XoMember(IsAttribute = true)]
        public double ToolsVersion {
            get { return XoMember.GetValue<double>(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }

        public new TfsVcxProjectConfiguration GetConfiguration(TfsConfiguration configuration) {
            return (TfsVcxProjectConfiguration)base.GetProjectConfiguration(configuration);
        }
        public new IEnumerable<TfsVcxProjectConfiguration> Configurations() {
            return base.ProjectConfigurations().Cast<TfsVcxProjectConfiguration>();
        }
    }
    public class TfsVcxProjectConfiguration : TfsProjectConfiguration {

        private XElement m_xml;

        internal TfsVcxProjectConfiguration(TfsVcxProject project, XElement xml)
            : base(project, TfsConfiguration.Parse((string)xml.Attribute("Include"))) {

            m_xml = xml;
        }

        internal override IEnumerable<XElement> Sections() {
            var result =
               (from o in Project.Xml.Descendants()
                from a in o.Attributes()
                where a.Value.Contains(Configuration.ToString())
                select o).ToList();

            result = result.Where(o => result.None(x => o.Ancestors().Contains(x))).ToList();

            return result;
        }
    }

    [TfsObject(Extension = ".vdproj", Guid = "54435603-DBB4-11D2-8724-00A0C9A8B90C")]
    public class TfsVdProject : TfsProject {

        public static string FromXml(XElement xml) {
            throw new NotImplementedException();
        }
        public static XElement ToXml(string path) {
            var lines = new StreamReader(path).ReadLines().ToQueue();
            return ToXmlVdSection(lines);
        }
        private static XElement ToXmlVdSection(Queue<string> lines) {
            var line = lines.Dequeue();
            var regex = Regex.Match(line, @"""(?<name>[^:]*)(:(?<value>[^""]*))?""");
            var name = regex.Get("name");
            var value = regex.Get("value");
            var xml = ToXmlVdValue(name, null, value);

            line = lines.Dequeue();
            if (line.Trim() != "{")
                throw new ArgumentException();

            while ((line = lines.Peek()).Trim() != "}") {
                if (line.Contains("=")) {
                    xml.Add(ToXmlVdSectionValue(lines));
                } else {
                    xml.Add(ToXmlVdSection(lines));
                }
            }

            line = lines.Dequeue();

            return xml;
        }
        private static XElement ToXmlVdSectionValue(Queue<string> lines) {
            var line = lines.Dequeue();
            var regex = Regex.Match(line, @"""(?<name>\w*)"" = ""(?<int>\d*):(?<value>[^""]*)""");
            var name = regex.Get("name");
            var type = regex.Get("int");
            var value = regex.Get("value");

            return ToXmlVdValue(name, type, value);
        }
        private static XElement ToXmlVdValue(string name, string type, string value) {

            value = value.Replace(@"\\", @"\");

            var isAlphaNumeric = name.All(o => char.IsLetterOrDigit(o));
            var xml = new XElement(isAlphaNumeric ? name.ToLowerFirst() : "section",
                isAlphaNumeric ? null : new XAttribute("name", name),
                string.IsNullOrEmpty(value) ? null : new XAttribute("value", value),
                type == null ? null : new XAttribute("type", type)
            );

            return xml;
        }

        private Dictionary<TfsConfiguration, TfsVdProjectConfiguration> m_configurations;
        private XElement m_xml;

        internal TfsVdProject(TfsCollection collection, Uri url)
            : base(collection, url) {
        }

        internal override IEnumerable<XObject> PathXmlReferences() {
            yield break;
        }
        protected override IEnumerable<TfsProjectConfiguration> GetConfigurations() {
            return Xml.Descendants("configurations").Elements()
                .Select(o => new TfsVdProjectConfiguration(this, o));
        }

        public override XElement Xml {
            get {
                if (m_xml == null)
                    m_xml = ToXml(Path);
                return m_xml;
            }
        }
        public new TfsVdProjectConfiguration GetConfiguration(TfsConfiguration configuration) {
            return (TfsVdProjectConfiguration)base.GetProjectConfiguration(configuration);
        }
        public new IEnumerable<TfsVdProjectConfiguration> Configurations() {
            return base.ProjectConfigurations().Cast<TfsVdProjectConfiguration>();
        }
    }
    public class TfsVdProjectConfiguration : TfsProjectConfiguration {

        private XElement m_xml;

        internal TfsVdProjectConfiguration(TfsVdProject project, XElement xml)
            : base(project, TfsConfiguration.Parse(xml.Name.LocalName.ToUpperFirst())) {

            m_xml = xml;
        }

        internal override IEnumerable<XElement> Sections() {
            yield break;
        }
    }

    [TfsObject(Extension = ".wdproj", Guid = "2CFEAB61-6A3B-4EB8-B523-560B4BEEF521",
        DefaultPlatform = TfsPlatform.AnyCpu)]
    public class TfsWdProject : TfsProject, ITfsSolutionProject {

        private Dictionary<TfsConfiguration, TfsWdProjectConfiguration> m_configurations;

        internal TfsWdProject(TfsCollection collection, Uri url)
            : base(collection, url) {
        }

        internal override IEnumerable<XObject> PathXmlReferences() {
            yield return Xml.Descendants(XNamespace + "SourceWebPhysicalPath").Single();
        }
        protected override IEnumerable<TfsProjectConfiguration> GetConfigurations() {
            return TfsCsProject.GetConfigruationXml(Xml)
                .Select(o => new TfsWdProjectConfiguration(this, o));
        }

        public new TfsWdProjectConfiguration GetProjectConfiguration(TfsConfiguration configuration) {
            return (TfsWdProjectConfiguration)base.GetProjectConfiguration(configuration);
        }
        public new IEnumerable<TfsWdProjectConfiguration> ProjectConfigurations() {
            return base.ProjectConfigurations().Cast<TfsWdProjectConfiguration>();
        }
    }
    public class TfsWdProjectConfiguration : TfsProjectConfiguration {

        private XElement m_xml;

        internal TfsWdProjectConfiguration(TfsWdProject project, XElement xml)
            : base(project, TfsCsProjectConfiguration.ParseConfiguration(xml)) {

            m_xml = xml;
        }

        internal override IEnumerable<XElement> Sections() {
            yield return m_xml;
        }
    }

    [XoType(NamesStartInLowerCase = true)]
    [TfsObject(Extension = ".sln", DependentFiles = "*.vssscc", PathAttributes = "path")]
    public class TfsSolution : TfsNode, ITfsConfigurable {

        public static string FromXml(XElement xml) {

            xml = PreProcess(xml);

            var result = new VSSolution() {
                Year = xml.Attribute("year").Value,
                Version = ((double)xml.Attribute("version")).ToString("##.00"),

                Projects =
                    from o in xml.Element("objects").Elements("object")
                    let typeName = typeof(TfsSolution).Namespace + "." + "Tfs" + o.Attribute("type").Value.ToUpperFirst()
                    let info = TfsLoader.GetInfo(Type.GetType(typeName))
                    let guid = (Guid)o.Attribute("guid")
                    let name = (string)o.Attribute("name")
                    let path = (string)o.Attribute("path") ?? name
                    select new VSProject(info.Guid, guid, name ?? SysPath.GetFileNameWithoutExtension(path), path) {
                        from s in o.Elements()
                        let isPre = (bool?)s.Attribute("isPre")
                        select new VSSection("ProjectSection", s.Name.LocalName.ToUpperFirst(), isPre == true ? "preProject" : "postProject") {
                            from e in s.Elements("value")
                            let lhs = (string)e.Attribute("key")
                            let rhs = (string)e.Attribute("value")
                            select new VSSectionItem(lhs, rhs)
                        }
                    },

                Global =
                    from o in xml.Element("global").Elements()
                    let name = o.Name.LocalName.ToUpperFirst()
                    let isPre = (bool?)o.Attribute("isPre")
                    select new VSSection("GlobalSection", name, isPre == true ? "preSolution" : "postSolution") {
                        from e in o.Elements("value")
                        let lhs = (string)e.Attribute("key")
                        let rhs = (string)e.Attribute("value")
                        select new VSSectionItem(lhs, rhs)
                    }
            }.ReadToEnd();

            return result;
        }
        public static XElement ToXml(string file) {

            var lines = new StreamReader(file).ReadLines().ToQueue();
            var xml = new XElement("solution");
            xml.Add(new XAttribute("name", SysPath.GetFileName(file)));

            while (string.IsNullOrEmpty(lines.Peek()))
                lines.Dequeue();

            var versionRegex = Regex.Match(lines.Dequeue(), "Microsoft Visual Studio Solution File, Format Version (?<version>.*)");
            var version = double.Parse(versionRegex.Groups["version"].Value);
            if (version != SupportedVersion)
                throw new NotSupportedException();
            xml.Add(new XAttribute("version", version));

            if (lines.Peek().StartsWith("# Visual Studio")) {
                var yearRegex = Regex.Match(lines.Dequeue(), "# Visual Studio (?<year>.*)");
                var year = int.Parse(yearRegex.Groups["year"].Value);
                xml.Add(new XAttribute("year", year));
            }

            var projects = new XElement("objects");
            xml.Add(projects);

            while (lines.Peek().StartsWith("Project")) {
                var line = lines.Dequeue();

                var projectRegex = Regex.Match(line,
                    "Project[(]\"{(?<type>[a-zA-Z0-9-]*)}\"[)] = " +
                    "\"(?<name>[^\"]*)\", \"(?<path>[^\"]*)\", \"{(?<guid>[a-zA-Z0-9-]*)}\"");

                var type = projectRegex.Get<Guid>("type");
                var info = TfsLoader.GetInfo(type);

                if (info == null)
                    throw new ArgumentException(string.Format("Solution {0} has a project with an unrecognized guid type {1}.", file, type));

                projects.Add(
                    new XElement("object",
                        new XAttribute("type", info.Type.Name.Substring(3).ToLowerFirst()),
                        info.IsSolutionFolder ? new XAttribute("name", projectRegex.Groups["name"].Value) : null,
                        info.IsSolutionFolder ? null : new XAttribute("path", projectRegex.Groups["path"].Value),
                        new XAttribute("guid", projectRegex.Groups["guid"].Value),
                        SectionsToXml(lines)
                     )
                );

                if (lines.Peek() != "EndProject")
                    throw new ArgumentException("Unable to parse solution file.");
                lines.Dequeue();
            }

            if (lines.Dequeue() != "Global")
                throw new ArgumentException("Cannot parse solution file: " + file);

            xml.Add(new XElement("global", SectionsToXml(lines)));

            if (lines.Dequeue() != "EndGlobal")
                throw new ArgumentException("Cannot parse solution file: " + file);

            PostProcess(xml);
            return xml;
        }
        private static void PostProcess(XElement xml) {

            var configXml = xml.Descendants("projectConfigurationPlatforms").SingleOrDefault();
            if (configXml != null) {
                configXml.ReplaceWith(
                    new XElement("projectConfigurationPlatforms",
                        from o in configXml.Elements()
                        let key = (string)o.Attribute("key")
                        let regex = Regex.Match(key, "{(?<guid>[^}]*)}[.](?<solution>.*)[.](?<state>(ActiveCfg|Build.0))")
                        group o by new {
                            project = regex.Get<Guid>("guid"),
                            solution = regex.Get("solution")
                        } into o group o by o.Key.solution into o
                        select new XElement("configuration",
                            new XAttribute("value", o.Key),
                            from p in o
                            let enabled = p.MoreThanOne()
                            select new XElement("project",
                                new XAttribute("guid", p.Key.project),
                                new XAttribute("value", (string)p.First().Attribute("value")),
                                enabled ? null : new XAttribute("disabled", true)
                            )
                        )
                        )
                    );

                var solutionConfig = xml.Descendants("solutionConfigurationPlatforms").Single();
                solutionConfig.RemoveAll();
            }

            var dependencies = xml.Descendants("projectDependencies").ToArray();
            foreach (var dependency in dependencies) {
                dependency.ReplaceWith(
                    new XElement("dependencies",
                        from o in dependency.Elements("value")
                        select new XElement("project",
                            new XAttribute("guid", (Guid)o.Attribute("key"))
                        )
                    )
                );
            }

            var tfs = xml.Descendants("teamFoundationVersionControl").SingleOrDefault();
            if (tfs != null) {
                var elements = tfs.Elements().ToList();

                tfs.ReplaceWith(
                    new XElement("teamFoundationVersionControl",
                        new XAttribute("server", (string)elements.Skip(2).First().Attribute("value")),
                        new XAttribute("provider", (Guid)elements.Skip(1).First().Attribute("value")),
                        from o in tfs.Elements().Skip(3)
                        group o by Regex.Match((string)o.Attribute("key"), @"\d*$").Value into o
                        let value = (string)o.First().Attribute("value")
                        select new XElement("object", new XAttribute("path", value.Replace(@"\\", @"\")))
                    )
                );
            }

            var items = xml.Descendants("solutionItems").ToArray();
            foreach (var item in items) {
                item.ReplaceWith(
                    new XElement("items",
                        from o in item.Elements("value")
                        select new XElement("item",
                            new XAttribute("path", (string)o.Attribute("key"))
                        )
                    )
                );
            }

            var nestedProjects = xml.Descendants("nestedProjects").ToArray();
            foreach (var nestedProject in nestedProjects) {
                nestedProject.ReplaceWith(
                    new XElement("folders",
                        from o in nestedProject.Elements("value")
                        let folder = (Guid)o.Attribute("value")
                        group o by folder into o
                        select new XElement("folder",
                            new XAttribute("guid", o.Key),
                            from n in o
                            select new XElement("object",
                                new XAttribute("guid", (Guid)n.Attribute("key"))
                            )
                        )
                    )
                );
            }
        }
        private static XElement PreProcess(XElement xml) {

            xml = new XElement(xml);
            var objects = xml.Elements("objects").Elements("object");
            var global = xml.Element("global");

            var dependencies = objects.Descendants("dependencies").ToArray();
            foreach (var dependency in dependencies) {
                dependency.ReplaceWith(
                    new XElement("projectDependencies",
                        new XAttribute("isPre", false),
                        from o in dependency.Elements("project")
                        let guid = new VSGuid((Guid)o.Attribute("guid")).ReadToEnd()
                        select new XElement("value",
                            new XAttribute("key", guid),
                            new XAttribute("value", guid)
                        )
                    )
                );
            }

            var items = objects.Descendants("items").ToArray();
            foreach (var item in items) {
                item.ReplaceWith(
                    new XElement("solutionItems",
                        new XAttribute("isPre", true),
                        from o in item.Elements("item")
                        let path = (string)o.Attribute("path")
                        orderby path
                        select new XElement("value",
                            new XAttribute("key", path),
                            new XAttribute("value", path)
                        )
                    )
                );
            }

            var tfs = global.Element("teamFoundationVersionControl");
            if (tfs != null) {
                var elements = tfs.Elements();
                var solutionName = (string)xml.Attribute("name");

                var nestedProjectsGuid = global.Elements("folders").Descendants("object").Attributes("guid").Select(o => (Guid)o).ToHashSet();
                var nestedProjectPaths = objects.Where(o => nestedProjectsGuid.Contains((Guid)o.Attribute("guid")))
                    .Select(o => (string)o.Attribute("path")).ToCaseInsensitiveHashSet();

                var tfsUrl = (string)tfs.Attribute("server");
                var provider = new VSGuid((Guid)tfs.Attribute("provider")).ReadToEnd();

                tfs.ReplaceWith(
                    new XElement("teamFoundationVersionControl",
                        new XAttribute("isPre", true),
                        GetSccXml("SccNumberOfProjects", elements.Count()),
                        GetSccXml("SccEnterpriseProvider", provider),
                        GetSccXml("SccTeamFoundationServer", tfsUrl),
                        elements.Select((o, i) => GetSccObject(tfsUrl, provider, solutionName, nestedProjectPaths, o, i))
                    )
                );
            }

            global.Descendants("solutionConfigurationPlatforms").Single().Add(
                new XAttribute("isPre", true),

                from o in global.Element("projectConfigurationPlatforms").Elements("configuration")
                let config = (string)o.Attribute("value")
                select new XElement("value",
                    new XAttribute("key", config),
                    new XAttribute("value", config)
                )
            );

            var folders = global.Element("folders");
            if (folders != null) {
                folders.ReplaceWith(
                    new XElement("NestedProjects",
                        new XAttribute("isPre", true),
                        from o in folders.Descendants("object")
                        let key = new VSGuid((Guid)o.Attribute("guid")).ReadToEnd()
                        let value = new VSGuid((Guid)o.Parent.Attribute("guid")).ReadToEnd()
                        select new XElement("value",
                            new XAttribute("key", key),
                            new XAttribute("value", value)
                        )
                    )
                );
            }

            var projectConfigPlatforms = global.Element("projectConfigurationPlatforms");
            if (projectConfigPlatforms != null) {
                projectConfigPlatforms.ReplaceWith(
                    new XElement("projectConfigurationPlatforms",
                        new XAttribute("isPre", false),

                        from o in projectConfigPlatforms.Descendants("project")
                        let guid = (Guid)o.Attribute("guid")
                        group o by guid into o
                        from x in o
                        let guid = new VSGuid(o.Key).ReadToEnd()
                        let projectConfig = (string)x.Attribute("value")
                        let solutionConfig = (string)x.Parent.Attribute("value")
                        let disabled = (bool?)x.Attribute("disabled") == true
                        let keyFormat = guid + "." + solutionConfig + "."
                        from k in new[] { 
                            keyFormat + "ActiveCfg",
                            disabled ? null : keyFormat + "Build.0"
                        } where k != null
                        select new XElement("value",
                            new XAttribute("key", k),
                            new XAttribute("value", projectConfig)
                        )
                    )
                );
            }

            return xml;
        }
        private static XElement GetSccXml(string name, object value) {
            return new XElement("value",
                new XAttribute("key", name),
                new XAttribute("value", value)
            );
        }
        private static IEnumerable<XElement> GetSccObject(string tfsUrl, string provider,
            string solutionName, HashSet<string> nestedProjectPaths, XElement o, int id) {

            var path = (string)o.Attribute("path");
            if (path == ".") {
                yield return GetSccXml("SccLocalPath" + id, path);

            } else if (!path.Contains(@"\")) {
                yield return GetSccXml("SccProjectUniqueName" + id, path.Replace(@"\", @"\\"));
                yield return GetSccXml("SccProjectName" + id, ".");
                yield return GetSccXml("SccAuxPath" + id, tfsUrl);
                yield return GetSccXml("SccLocalPath" + id, ".");
                yield return GetSccXml("SccProvider" + id, provider);

            } else {
                var dir = SysPath.GetDirectoryName(path);
                yield return GetSccXml("SccProjectUniqueName" + id, path.Replace(@"\", @"\\"));
                if (nestedProjectPaths.Contains(path))
                    yield return GetSccXml("SccProjectTopLevelParentUniqueName" + id, solutionName);
                yield return GetSccXml("SccProjectName" + id, dir.Replace(@"\", "/"));
                yield return GetSccXml("SccAuxPath" + id, tfsUrl);
                yield return GetSccXml("SccLocalPath" + id, dir.Replace(@"\", @"\\"));
                yield return GetSccXml("SccProvider" + id, provider);
            }
        }
        private static IEnumerable<XElement> SectionsToXml(Queue<string> lines) {
            while (true) {
                var peek = lines.Peek();
                var match = Regex.Match(peek, @"\t(?<global>Global|Project)Section[(](?<name>\w*)[)] = (?<pre>pre|post)(Solution|Project)");
                if (!match.Success)
                    yield break;
                lines.Dequeue();

                var name = match.Groups["name"].Value.ToLowerFirst();

                var isGlobal = match.Groups["global"].Value == "Global";
                var isPre = match.Groups["pre"].Value == "pre";

                var section = isGlobal ? "GlobalSection" : "ProjectSection";
                var endSection = "End" + section;
                var prePost = (isPre ? "pre" : "post") + (isGlobal ? "Solution" : "Project");

                var keyValuePairs = new List<KeyValuePair<string, string>>();
                while (lines.Peek() != "\t" + endSection) {
                    var itemRegex = Regex.Match(lines.Dequeue(), "\t\t(?<lhs>[^=]*)=(?<rhs>.*)");
                    var lhs = itemRegex.Groups["lhs"].Value.Trim();
                    var rhs = itemRegex.Groups["rhs"].Value.Trim();

                    keyValuePairs.Add(new KeyValuePair<string, string>(lhs, rhs));
                }

                yield return new XElement(name,
                    new XAttribute("isPre", isPre),
                    from o in keyValuePairs
                    select new XElement("value",
                        new XAttribute("key", o.Key),
                        new XAttribute("value", o.Value)
                    )
                );

                lines.Dequeue();
            }
        }

        private static double SupportedVersion = 11;

        private XElement m_xml;
        private TfsSolutionFolder m_rootFolder;
        private TfsPlatform? m_defaultUnmanagedPlatform;

        private Dictionary<Guid, ITfsSolutionObject> m_objectsByGuid;
        private Dictionary<Guid, XElement> m_objectXmlByGuid;
        private Dictionary<TfsConfiguration, TfsSolutionConfiguration> m_solutionConfigurations;

        internal TfsSolution(TfsFolder folder, string name, int year, string defaultUnmanagedPlatform)
            : this(folder.Collection, folder.GetUrl(name)) {

            Clear(year, defaultUnmanagedPlatform);
        }
        internal TfsSolution(TfsCollection collection, Uri url)
            : base(collection, url) {

            Initialize();
        }

        private void Initialize() {
            m_rootFolder = new TfsSolutionFolder(this, "__root");
        }
        private void CacheObject(XElement xml) {
            var guid = (Guid)xml.Attribute("guid");

            var typeName = GetType().Namespace + "." + "Tfs" + xml.Attribute("type").Value.ToUpperFirst();
            var info = TfsLoader.GetInfo(Type.GetType(typeName));

            if (info.IsSolutionObject)
                m_objectsByGuid[guid] = (ITfsSolutionObject)info.Type.Activate(this, xml);
            else
                m_objectXmlByGuid[guid] = xml;
        }
        private TfsSolutionConfiguration CacheConfiguration(XElement xml) {
            var configuration = TfsConfiguration.Parse((string)xml.Attribute("value"));
            return m_solutionConfigurations[configuration] = new TfsSolutionConfiguration(this, xml);
        }

        internal override IEnumerable<XObject> PathXmlReferences() {
            return base.PathXmlReferences().Where(o => o.GetValue() != ".");
        }
        internal override void Update() {
            using (var stream = new StreamWriter(Path, false, Encoding.UTF8))
                stream.Write(ToSolutionFile());
        }
        internal void Clear(int year, string defaultUnmanagedPlatform) {
            var version = year == 2010 ? 11 : 10;
            m_defaultUnmanagedPlatform = TfsConfiguration.GetPlatform(defaultUnmanagedPlatform);

            Initialize();

            m_xml = new XElement("solutions",
                new XAttribute("version", version),
                new XAttribute("year", year),
                new XElement("objects"),
                new XElement("global",
                    new XElement("solutionConfigurationPlatforms"),
                    new XElement("projectConfigurationPlatforms"),
                    new XElement("solutionProperties",
                        new XAttribute("isPre", true),
                        new XElement("value",
                            new XAttribute("key", "hideSolutionNode"),
                            new XAttribute("value", "FALSE")
                        )
                    ),
                    new XElement("folders")
                )
            );

            m_xml.Changed += delegate(object sender, XObjectChangeEventArgs e) { Touch(); };

            AddConfiguration(TfsConfiguration.DebugMixedPlatforms);
            AddConfiguration(TfsConfiguration.ReleaseMixedPlatforms);

            Touch();
        }
        internal void CacheProject(TfsProject project) {
            var info = project.Info;

            var guid = (Guid)project.Guid;

            var xml = new XElement("object",
                new XAttribute("type", info.XName),
                new XAttribute("path", PathUrl.MakeRelativePath(project.PathUrl)),
                new XAttribute("guid", guid)
            );
            m_xml.Element("objects").Add(xml);
            CacheObject(xml);

            foreach (var solutionConfiguration in SolutionConfigurations()) {
                var configuration = solutionConfiguration.Configuration;

                // use exact match of solution\project platform and configuration
                var projectConfiguration = project.GetProjectConfiguration(configuration);

                if (projectConfiguration == null) {

                    var projectConfigurations = project.ProjectConfigurations()
                        .Where(o => o.Flavor == configuration.Flavor).ToArray();

                    // use match of solution\project configuration but only if there is one match
                    projectConfiguration = projectConfigurations.FirstOrDefault();
                    if (projectConfigurations.MoreThanOne()) {

                        // if solution is MixedPlatforms then use the default platform specific to the project type
                        if (configuration.Platform == TfsPlatform.MixedPlatforms) {

                            var defaultPlatform = info.DefaultPlatform;
                            if (info.IsUnmanagedProject && m_defaultUnmanagedPlatform != null)
                                defaultPlatform = m_defaultUnmanagedPlatform;

                            projectConfiguration = projectConfigurations.SingleOrDefault(o => o.Platform == defaultPlatform);
                        }
                    }
                }

                if (projectConfiguration == null)
                    throw new ArgumentException("Failed to match solution platform and configuration with project platform and configuration.");

                solutionConfiguration.AddProjectConfiguration(projectConfiguration);
            }
        }
        internal void UnCacheProject(ITfsSolutionProject project) {
            var folder = SolutionFolders().SingleOrDefault(o => o.SolutionProjects().Contains(project));
            if (folder != null)
                throw new ArgumentException("Remove project by removing it from the solution folder.");

            throw new NotImplementedException();
        }
        internal XElement GetObjectXml(Guid guid) {
            return m_objectXmlByGuid.GetValueOrDefault(guid);
        }

        public TfsSolutionFolder RootSolutionFolder {
            get { return m_rootFolder; }
        }

        public IEnumerable<TfsSolutionConfiguration> SolutionConfigurations(TfsProjectConfiguration projectConfiguration) {
            return SolutionConfigurations().Where(o => o.ProjectConfigurations().Contains(projectConfiguration));
        }
        public IEnumerable<TfsSolutionConfiguration> SolutionConfigurations() {
            if (m_solutionConfigurations == null) {

                m_solutionConfigurations = new Dictionary<TfsConfiguration, TfsSolutionConfiguration>();

                foreach (var xml in Xml.Descendants("configuration"))
                    CacheConfiguration(xml);
            }

            return m_solutionConfigurations.Values;
        }
        public IEnumerable<TfsConfiguration> Configurations(TfsProjectConfiguration projectConfiguration) {
            return SolutionConfigurations(projectConfiguration).Select(o => o.Configuration);
        }
        public IEnumerable<TfsConfiguration> Configurations() {
            return SolutionConfigurations().Select(o => o.Configuration);
        }
        public IEnumerable<TfsProjectConfiguration> ProjectConfigurations() {
            var projectConfigurations = SolutionConfigurations().SelectMany(o => o.ProjectConfigurations());
            var result = projectConfigurations.OrderBy(o => o.Configuration.ToString()).ToArray();
            return result;
        }
        public TfsSolutionConfiguration GetSolutionConfiguration(TfsConfiguration configuration) {
            if (m_solutionConfigurations == null)
                SolutionConfigurations();
            return m_solutionConfigurations.GetValueOrDefault(configuration);
        }
        public void RemoveConfiguration(TfsConfiguration configuration) {
            var solutionConfiguration = GetSolutionConfiguration(configuration);
            if (solutionConfiguration == null)
                throw new ArgumentException();
            solutionConfiguration.Remove();
        }
        public TfsConfiguration DefaultConfiguration {
            get { return null; }
            set { throw new InvalidOperationException(); }
        }

        public ILookup<ITfsSolutionProject, ITfsSolutionProject> Dependencies() {
            var result =
                from o in Xml.Element("objects").Elements("object")
                from d in o.Elements("dependencies").Elements("project")
                select new {
                    Object = GetSolutionProject((Guid)o.Attribute("guid")),
                    Dependency = GetSolutionProject((Guid)d.Attribute("guid"))
                };

            return result.ToLookup(o => o.Object, o => o.Dependency);
        }

        public IEnumerable<TfsSolutionFolder> SolutionFolders() {
            return Objects().WhereIs<TfsSolutionFolder>();
        }
        public TfsSolutionFolder GetSolutionFolder(Guid guid) {
            return (TfsSolutionFolder)GetObject(guid);
        }
        public IEnumerable<ITfsSolutionProject> SolutionProjects() {
            return Objects().WhereIs<ITfsSolutionProject>();
        }
        public IEnumerable<TfsProject> Projects() {
            return Objects().WhereIs<TfsProject>();
        }
        public ITfsSolutionProject GetSolutionProject(Guid guid) {
            return (ITfsSolutionProject)GetObject(guid);
        }
        public IEnumerable<ITfsSolutionObject> Objects() {

            if (m_objectsByGuid == null) {
                m_objectsByGuid = new Dictionary<Guid, ITfsSolutionObject>();
                m_objectXmlByGuid = new Dictionary<Guid, XElement>();

                var objects = Xml.Element("objects");

                foreach (var xml in objects.Elements("object"))
                    CacheObject(xml);
            }

            var solutionObjects = m_objectsByGuid.Values;
            var projects = m_objectXmlByGuid.Keys.Select(o => GetObject(o));
            return solutionObjects.Concat(projects.Cast<ITfsSolutionObject>());
        }
        public ITfsSolutionObject GetObject(Guid guid) {
            if (m_objectsByGuid == null)
                Objects();

            var xml = m_objectXmlByGuid.GetValueOrDefault(guid);
            if (xml == null)
                return m_objectsByGuid.GetValueOrDefault(guid);

            return (ITfsSolutionObject)GetPath((string)xml.Attribute("path"));
        }
        public bool ContainsObject(ITfsSolutionObject o) {
            return Objects().Contains(o);
        }

        public TfsSolutionConfiguration AddConfiguration(TfsConfiguration configuration) {
            if (Objects().Any())
                throw new NotImplementedException();

            var xml = new XElement("configuration",
                new XAttribute("value", configuration.ToString())
            );

            Xml.Descendants("projectConfigurationPlatforms").Single().Add(xml);

            if (m_solutionConfigurations == null)
                SolutionConfigurations();

            return CacheConfiguration(xml);
        }
        public void RemoveProject(TfsProject project) {
            m_rootFolder.RemoveItem(project);
        }
        public void AddProject(TfsProject project) {
            m_rootFolder.AddItem(project);
        }
        public TfsSolutionFolder AddSolutionFolder(string name) {
            return AddSolutionFolder(name, Guid.NewGuid().ToString());
        }
        public TfsSolutionFolder AddSolutionFolder(string name, string guidKey) {
            var guid = new Guid(MD5.Create().ComputeHash(guidKey.ToStream()));

            var xml = new XElement("object",
                new XAttribute("type", "solutionFolder"),
                new XAttribute("name", name),
                new XAttribute("guid", guid)
            );
            Xml.Element("objects").Add(xml);

            CacheObject(xml);

            var solutionFolder = GetSolutionFolder(guid);

            return solutionFolder;
        }
        public void AddDependency(ITfsSolutionProject project, ITfsSolutionProject dependentProject) {

            if (GetObject(project.GetGuid(this)) == null)
                throw new ArgumentException("Solution does not contain project with guid: " + project.GetGuid(this));

            if (GetObject(dependentProject.GetGuid(this)) == null)
                throw new ArgumentException("Solution does not contain project with guid: " + project.GetGuid(this));

            var projectXml = GetObjectXml(project.GetGuid(this));

            var dependencies = projectXml.Element("dependencies");
            if (dependencies == null)
                projectXml.Add(dependencies = new XElement("dependencies"));
            dependencies.Add(new XElement("project",
                new XAttribute("guid", dependentProject.GetGuid(this))));
        }

        public override XElement Xml {
            get {
                if (m_xml == null) {
                    m_xml = ToXml(Path);
                    m_xml.Changed += delegate(object sender, XObjectChangeEventArgs e) { Touch(); };
                }
                return m_xml;
            }
        }
        public string ToSolutionFile() {
            return FromXml(Xml);
        }

        [XoMember(IsAttribute = true)]
        public bool HideSolutionNode {
            get { return XoMember.GetValue<bool>(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        [XoMember(IsAttribute = true)]
        public int Year {
            get { return XoMember.GetValue<int>(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }
        [XoMember(IsAttribute = true)]
        public double Version {
            get { return XoMember.GetValue<double>(MethodInfo.GetCurrentMethod(), this); }
            set { XoMember.SetValue(MethodInfo.GetCurrentMethod(), this, value); }
        }

        public override IEnumerable<TfsItem> DependentItems() {
            foreach (var o in base.DependentItems())
                yield return o;

            foreach (var o in SolutionFolders().SelectMany(o => o.Items()))
                yield return o;
        }
    }

    public class TfsSolutionSection : IEnumerable<KeyValuePair<string, string>> {

        private bool m_isGlobal;
        private bool m_isPre;
        private string m_name;
        private IEnumerable<KeyValuePair<string, string>> m_values;

        public TfsSolutionSection(bool isGlobal, bool isPre, string name, IEnumerable<KeyValuePair<string, string>> values) {
            m_isGlobal = isGlobal;
            m_isPre = isPre;
            m_name = name;
            m_values = values;
        }

        public bool IsGlobal {
            get { return m_isGlobal; }
        }
        public bool IsPre {
            get { return m_isPre; }
        }
        public string Name {
            get { return m_name; }
        }

        public IEnumerator<KeyValuePair<string, string>> GetEnumerator() {
            return m_values.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator() {
            return m_values.GetEnumerator();
        }
    }
    public interface ITfsSolutionObject {
        Guid GetGuid(TfsSolution solution);
        string Name { get; }
    }
    public interface ITfsSolutionProject : ITfsSolutionObject, ITfsConfigurable {
        TfsProjectConfiguration GetProjectConfiguration(TfsConfiguration configuration);
        IEnumerable<TfsProjectConfiguration> ProjectConfigurations();
    }
    internal static class Extensions {
        internal static string GetPath(this ITfsSolutionObject solutionObject) {
            var item = solutionObject as TfsItem;
            if (item == null)
                return solutionObject.Name;

            return item.Path;
        }
        internal static string GetRelPath(this ITfsSolutionObject solutionObject, TfsSolution solution) {
            var item = solutionObject as TfsItem;
            if (item == null)
                return solutionObject.Name;

            return solution.PathUrl.MakeRelativePath(item.PathUrl);
        }
    }

    internal class TfsPlatformAttribute : Attribute {
        private string m_name;
        public TfsPlatformAttribute(string name) {
            m_name = name;
        }
        public string Name {
            get { return m_name; }
        }
        public object ProcessorArchitecture { get; set; }
    }
    public enum TfsFlavor {
        Debug = 1,
        Release,
        Checked,
    }
    public enum TfsPlatform {
        [TfsPlatform("Mixed Platforms")]
        MixedPlatforms = 1,
        [TfsPlatform("AnyCPU")]
        AnyCpu,
        [TfsPlatform("x86", ProcessorArchitecture = "x86")]
        X86,
        [TfsPlatform("x64", ProcessorArchitecture = "AMD64")]
        X64,
        [TfsPlatform("Win32")]
        Win32,
        [TfsPlatform(".NET")]
        DotNet,
    }
    public class TfsConfiguration {

        public static TfsConfiguration DebugAnyCpu = Get("Debug", "AnyCPU");
        public static TfsConfiguration ReleaseAnyCpu = Get("Release", "AnyCPU");
        public static TfsConfiguration DebugMixedPlatforms = Get("Debug", "Mixed Platforms");
        public static TfsConfiguration ReleaseMixedPlatforms = Get("Release", "Mixed Platforms");

        public static TfsConfiguration Parse(string configuration) {
            if (s_configurations == null)
                s_configurations = new Dictionary<string, TfsConfiguration>(StringComparer.InvariantCultureIgnoreCase);

            configuration = configuration.Replace("Any CPU", "AnyCPU");

            var result = s_configurations.GetValueOrDefault(configuration);
            if (result == null)
                s_configurations[configuration] = result = new TfsConfiguration(configuration);

            return result;
        }
        public static TfsConfiguration Get(TfsFlavor flavor, TfsPlatform platform) {
            return Get(flavor.ToString(), platform.GetCustomAttribute<TfsPlatformAttribute>().Name);
        }
        public static TfsConfiguration Get(string flavor, string platform) {
            return Parse(flavor + "|" + platform);
        }

        public static TfsFlavor GetFlavor(string flavor) {
            var fieldInfo = typeof(TfsFlavor).Get<FieldInfo>(isStatic: true).SingleOrDefault(o => o.Name == flavor);
            if (fieldInfo == null)
                return default(TfsFlavor);
            return (TfsFlavor)fieldInfo.GetValue();
        }
        public static TfsPlatform GetPlatform(string platform) {
            var fieldInfo = typeof(TfsPlatform).Get<FieldInfo>(isStatic: true)
                .Single(o => o.GetAttribute<TfsPlatformAttribute>().Name == platform);
            if (fieldInfo == null)
                throw new ArgumentException();
            return (TfsPlatform)fieldInfo.GetValue();
        }
        public static TfsPlatform? GetDefaultUnmanagedPlatform() {
            var processorArchitecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
            var fieldInfo = typeof(TfsPlatform).Get<FieldInfo>(isStatic: true)
                .Single(o => o.GetAttribute<TfsPlatformAttribute>().ProcessorArchitecture.ToEnumerable<string>()
                    .Contains(processorArchitecture));
            if (fieldInfo == null)
                return null;
            return (TfsPlatform)fieldInfo.GetValue();
        }

        private static Dictionary<string, TfsConfiguration> s_configurations;

        private string m_configuration;
        private string m_platform;

        private TfsConfiguration(string configuration) {
            var split = configuration.Split('|');
            m_configuration = split[0];
            if (split.Length > 1)
                m_platform = split[1];
        }

        public string FlavorString {
            get { return m_configuration; }
        }
        public string PlatformString {
            get { return m_platform; }
        }
        public TfsFlavor Flavor {
            get {
                if (m_configuration == null)
                    return default(TfsFlavor);
                return GetFlavor(m_configuration);
            }
        }
        public TfsPlatform Platform {
            get {
                if (m_platform == null)
                    return default(TfsPlatform);
                return GetPlatform(m_platform);
            }
        }

        public override string ToString() {
            return string.Join("|", new[] { m_configuration, m_platform });
        }
    }

    public abstract class TfsConfigurableConfiguration {

        private ITfsConfigurable m_configurable;
        private TfsConfiguration m_configuration;

        internal TfsConfigurableConfiguration(ITfsConfigurable configurable, TfsConfiguration configuration) {
            m_configurable = configurable;
            m_configuration = configuration;
        }

        internal virtual IEnumerable<XElement> Sections() {
            return Enumerable.Empty<XElement>();
        }
        internal void Remove() {
            foreach (var section in Sections())
                section.Remove();
        }

        public ITfsConfigurable Configurable {
            get { return m_configurable; }
        }
        public TfsPlatform Platform {
            get { return Configuration.Platform; }
        }
        public TfsFlavor Flavor {
            get { return Configuration.Flavor; }
        }
        public TfsConfiguration Configuration {
            get { return m_configuration; }
        }

        public override string ToString() {
            return m_configuration.ToString();
        }
    }
    public sealed class TfsSolutionConfiguration : TfsConfigurableConfiguration {

        private XElement m_xml;

        internal TfsSolutionConfiguration(TfsSolution solution, XElement xml)
            : base(solution, TfsConfiguration.Parse((string)xml.Attribute("value"))) {
            m_xml = xml;
        }

        internal void AddProjectConfiguration(TfsProjectConfiguration projectConfiguration) {
            m_xml.Add(
                new XElement("project",
                    new XAttribute("guid", projectConfiguration.Project.Guid.ToString().ToLower()),
                    new XAttribute("value", projectConfiguration.ToString())
                )
            );
        }
        internal override IEnumerable<XElement> Sections() {
            yield return m_xml;
        }

        public TfsSolution Solution {
            get { return (TfsSolution)Configurable; }
        }

        public void SetEnabled(TfsProjectConfiguration projectConfiguration, bool enabled) {
            if (IsEnabled(projectConfiguration) == enabled)
                return;

            var xml = m_xml.Elements().Single(o => (Guid)o.Attribute("guid") == projectConfiguration.Project.Guid);
            var attribute = xml.Attribute("disabled");
            if (enabled) {
                if (attribute != null)
                    attribute.Remove();

            } else {
                if (attribute == null)
                    xml.Add(attribute = new XAttribute("disabled", true));
                attribute.SetValue(true);
            }
        }
        public bool IsEnabled(TfsProjectConfiguration projectConfiguration) {
            if (EnabledProjectConfigurations().Contains(projectConfiguration))
                return true;

            if (DisabledProjectConfigurations().Contains(projectConfiguration))
                return false;

            throw new ArgumentException();
        }

        public IEnumerable<TfsProjectConfiguration> ProjectConfigurations(bool enabled) {
            var result =
                from o in m_xml.Elements()
                let project = Solution.GetSolutionProject((Guid)o.Attribute("guid"))
                let configuration = TfsConfiguration.Parse((string)o.Attribute("value"))
                where !enabled == ((bool?)o.Attribute("disabled") ?? true)
                select project.GetProjectConfiguration(configuration);

            return result;
        }
        public IEnumerable<TfsProjectConfiguration> ProjectConfigurations() {
            return EnabledProjectConfigurations().Concat(DisabledProjectConfigurations());
        }
        public IEnumerable<TfsProjectConfiguration> EnabledProjectConfigurations() {
            return ProjectConfigurations(true);
        }
        public IEnumerable<TfsProjectConfiguration> DisabledProjectConfigurations() {
            return ProjectConfigurations(false);
        }
    }
    public abstract class TfsProjectConfiguration : TfsConfigurableConfiguration {

        internal TfsProjectConfiguration(ITfsSolutionProject project, TfsConfiguration configuration)
            : base(project, configuration) {
        }

        public TfsProject Project {
            get { return Configurable as TfsProject; }
        }
    }

    [TfsObject(Guid = "2150E333-8FDC-42A3-9474-1A3956D46DE8", SolutionObject = true)]
    public class TfsSolutionFolder : ITfsSolutionObject {

        private XElement m_objectXml;
        private XElement m_folderXml;
        private Uri m_url;
        private TfsSolutionFolder m_parent;
        private TfsSolution m_solution;
        private Guid m_guid;

        internal TfsSolutionFolder(TfsSolution solution, string name) {
            m_solution = solution;
            m_objectXml = new XElement("object",
                new XAttribute("type", "solutionFolder"),
                new XAttribute("name", name),
                new XAttribute("guid", new Guid(MD5.Create().ComputeHash(name.ToStream())))
            );
        }
        internal TfsSolutionFolder(TfsSolution solution, XElement xml) {
            m_solution = solution;
            m_guid = (Guid)xml.Attribute("guid");
            m_objectXml = xml;
        }

        private bool IsRoot {
            get { return Name == "__root"; }
        }
        private XElement FolderXml {
            get {
                if (m_folderXml == null) {
                    var foldersXml = Solution.Xml.Element("global").Element("folders");
                    var folderXml = foldersXml.Elements("folder").SingleOrDefault(o => (Guid)o.Attribute("guid") == m_guid);
                    if (folderXml == null)
                        foldersXml.Add(folderXml = new XElement("folder", new XAttribute("guid", m_guid)));
                    m_folderXml = folderXml;
                }

                return m_folderXml;
            }
        }

        internal IEnumerable<ITfsSolutionObject> Objects() {
            return Items().WhereIs<ITfsSolutionObject>().Concat(Folders().Cast<ITfsSolutionObject>());
        }
        internal void CacheItem(TfsItem item) {
            if (item is ITfsSolutionProject) {
                var project = (ITfsSolutionProject)item;
                FolderXml.Add(new XElement("object",
                    new XAttribute("guid", project.GetGuid(m_solution)))
                );

            } else {
                var items = m_objectXml.Element("items");
                if (items == null)
                    m_objectXml.Add(items = new XElement("items"));

                items.Add(new XElement("item",
                    new XAttribute("path", Solution.PathUrl.MakeRelativePath(item.PathUrl))
                ));
            }
        }

        public Uri Url {
            get {
                if (m_url == null) {
                    var url = m_solution.Url;
                    var parent = Parent;
                    if (parent != null)
                        url = parent.Url;

                    m_url = new Uri(url, Name).NormalizeUrlDirectory();
                }

                return m_url;
            }
        }
        public TfsSolutionFolder Parent {
            get {
                if (IsRoot)
                    return null;
                if (m_parent == null)
                    m_parent = m_solution.SolutionFolders().SingleOrDefault(o => o.Folders().Contains(this));
                if (m_parent == null)
                    m_parent = Solution.RootSolutionFolder;
                return m_parent;
            }
        }
        public void AddSubFolder(TfsSolutionFolder folder) {
            if (folder.Solution != Solution)
                throw new ArgumentException();

            FolderXml.Add(new XElement("object",
                new XAttribute("guid", folder.m_guid))
            );
        }
        public void AddItem(TfsItem item) {
            if (item is TfsProject)
                m_solution.CacheProject((TfsProject)item);
            CacheItem(item);
        }
        public void RemoveItem(TfsItem item) {
            if (item is ITfsSolutionProject)
                m_solution.UnCacheProject((ITfsSolutionProject)item);
            throw new NotImplementedException();
        }

        public IEnumerable<TfsItem> Items() {
            return m_objectXml.Elements("items").Elements().Select(o => m_solution.GetItem((string)o.Attribute("path")));
        }
        public IEnumerable<ITfsSolutionProject> SolutionProjects() {
            return Items().WhereIs<ITfsSolutionProject>();
        }
        public IEnumerable<TfsProject> Projects() {
            return Items().WhereIs<TfsProject>();
        }
        public IEnumerable<TfsSolutionFolder> Folders() {
            var values = m_solution.Xml.Element("global").Elements("nestedProjects").Elements("value");

            if (IsRoot) {
                var nestedGuids = values.Attributes("key").ToHashSet(o => (Guid)o);
                return Solution.SolutionFolders().ExceptWhere(o => nestedGuids.Contains(o.m_guid));
            }

            var result =
                from o in values
                where (Guid)o.Attribute("value") == m_guid
                select m_solution.GetObject((Guid)o.Attribute("key"));
            return result.WhereIs<TfsSolutionFolder>();
        }

        public TfsSolution Solution {
            get { return m_solution; }
        }
        public Guid GetGuid(TfsSolution solution) {
            if (m_guid == null) {
                var md5 = MD5.Create();
                var hash = md5.ComputeHash(Url.AbsolutePath.ToStream());
                m_guid = new Guid(hash);
            }
            return (Guid)m_guid;
        }
        public string Name {
            get { return (string)m_objectXml.Attribute("name"); }
        }

        public override string ToString() {
            return Name;
        }
    }

    [TfsObject(Guid = "E24C65DC-7377-472B-9ABA-BC803B73C61A", SolutionObject = true)]
    public class TfsWebPublishProject : ITfsSolutionProject {

        private XElement m_xml;
        private TfsSolution m_solution;
        private Dictionary<TfsConfiguration, TfsWebPublishProjectConfiguration> m_configurations;

        internal TfsWebPublishProject(TfsSolution solution, XElement xml) {
            m_solution = solution;
            m_xml = xml;
            m_configurations = new[] {
                new TfsWebPublishProjectConfiguration(this, TfsConfiguration.DebugAnyCpu),
                new TfsWebPublishProjectConfiguration(this, TfsConfiguration.ReleaseAnyCpu)
            }.ToDictionary(o => o.Configuration);
        }

        public Guid GetGuid(TfsSolution solution) {
            return (Guid)m_xml.Attribute("guid");
        }
        public TfsSolution Solution {
            get { return m_solution; }
        }
        public string Name {
            get { return (string)m_xml.Attribute("name"); }
        }

        public TfsProjectConfiguration GetProjectConfiguration(TfsConfiguration configuration) {
            return m_configurations.GetValueOrDefault(configuration);
        }
        public IEnumerable<TfsProjectConfiguration> ProjectConfigurations() {
            return m_configurations.Values;
        }

        public IEnumerable<TfsConfiguration> Configurations() {
            return m_configurations.Keys;
        }
        public void RemoveConfiguration(TfsConfiguration configuration) {
            throw new InvalidOperationException();
        }
        public TfsConfiguration DefaultConfiguration {
            get { return null; }
            set { throw new InvalidOperationException(); }
        }
    }
    public class TfsWebPublishProjectConfiguration : TfsProjectConfiguration {

        internal TfsWebPublishProjectConfiguration(TfsWebPublishProject project, TfsConfiguration configuration)
            : base(project, configuration) {
        }
    }
}
